Skip to main content
Component: NSIS Windows Installer

File: installer/Installer.nsi Platform: Windows 10+ Build Tool: NSIS 3.08+

Overview

The NSIS (Nullsoft Scriptable Install System) installer is GAIA’s primary Windows distribution mechanism. It creates a single-file executable that packages Python, GAIA source, dependencies, and configuration into a seamless installation experience. Key Features:
  • Single-exe distribution
  • No admin rights required (user-level install)
  • Embedded Python 3.10.9
  • Silent install support
  • Custom install paths
  • Progress reporting with DetailPrint
  • Automatic dependency checking (Lemonade, FFmpeg)

Requirements

Functional Requirements

  1. Installation Flow
    • Welcome page with version display
    • License agreement (MIT)
    • Directory selection (default: $LOCALAPPDATA\GAIA)
    • Optional components selection (RAUX UI)
    • Progress indication during install
    • Finish page with launch options
  2. Prerequisite Handling
    • Check for existing installation (prompt to remove)
    • Download/install Lemonade if missing
    • Install FFmpeg via winget if missing
    • Verify versions with Python script
  3. File Operations
    • Extract GAIA source to install directory
    • Download embedded Python
    • Configure Python environment
    • Create bin directory structure
    • Install launch scripts
  4. Post-Install Configuration
    • Update user PATH environment variable
    • Create desktop shortcuts (non-silent only)
    • Launch RAUX installer if selected
    • Offer to launch GAIA CLI

Non-Functional Requirements

  1. User Experience
    • Modern UI (MUI2)
    • Clear progress messages
    • Informative error messages
    • Silent mode for automation
  2. Reliability
    • Comprehensive error checking
    • Atomic operations (all-or-nothing)
    • Cleanup on failure
    • Logging to DetailPrint
  3. Compatibility
    • Windows 10+
    • 64-bit only
    • No admin rights required
    • Respects existing installations

System Architecture

Installation Flow Diagram

┌─────────────────┐
│  User Launches  │
│  setup.exe      │
└────────┬────────┘

    ┌────▼─────────────────────┐
    │  Check Existing Install  │
    │  (Prompt to remove)      │
    └────┬─────────────────────┘

    ┌────▼──────────────────┐
    │  Select Components    │
    │  ☑ GAIA (required)   │
    │  ☐ RAUX UI (optional)│
    └────┬──────────────────┘

    ┌────▼─────────────────────┐
    │  Download/Extract        │
    │  - Embedded Python       │
    │  - docopt.py (workaround)│
    └────┬─────────────────────┘

    ┌────▼──────────────────┐
    │  Check Prerequisites  │
    │  - Lemonade (prompt)  │
    │  - FFmpeg (winget)    │
    └────┬──────────────────┘

    ┌────▼───────────────────┐
    │  Install GAIA Package  │
    │  (via install.bat)     │
    └────┬───────────────────┘

    ┌────▼──────────────────┐
    │  Configure System     │
    │  - Update PATH        │
    │  - Create shortcuts   │
    │  - Launch RAUX setup  │
    └────┬──────────────────┘

    ┌────▼───────────┐
    │  Finish Page   │
    │  - Launch CLI? │
    │  - Launch RAUX?│
    └────────────────┘

NSIS Script Structure

Installer.nsi
├── Header Section
│   ├── Command-line parameters (!define)
│   ├── Variables (Var)
│   └── MUI2 settings

├── Pages
│   ├── Welcome (MUI_PAGE_WELCOME)
│   ├── License (MUI_PAGE_LICENSE)
│   ├── Directory (MUI_PAGE_DIRECTORY)
│   ├── RAUX Options (custom page)
│   ├── Install (MUI_PAGE_INSTFILES)
│   └── Finish (custom page)

├── Main Install Section
│   ├── Cleanup existing install
│   ├── Create directory structure
│   ├── Download Python
│   ├── Configure Python
│   ├── Install dependencies
│   └── Install GAIA

└── Functions
    ├── .onInit
    ├── RAUXOptionsPage
    ├── CustomFinishPage
    ├── run_raux_installer
    ├── check_raux_install
    ├── uninstall_raux
    ├── RunGAIACLI
    └── RunRAUX

API Specification

Command-Line Interface

Syntax:
gaia-windows-setup.exe [/S] [/D=<path>]
Parameters:
  • /S - Silent install (no UI, automatic defaults)
  • /D=<path> - Custom installation directory (must be last parameter)
Examples:
# Interactive install
.\gaia-windows-setup.exe

# Silent install to default location
.\gaia-windows-setup.exe /S

# Custom install path
.\gaia-windows-setup.exe /D=C:\MyApps\GAIA

# Silent install to custom path
.\gaia-windows-setup.exe /S /D=D:\Tools\GAIA

Build-Time Variables

Defined in version.nsh:
!define GAIA_VERSION "1.0.0"
!define LEMONADE_VERSION "8.0.4"
Hardcoded in Installer.nsi:
!define RAUX_VERSION "v0.6.5+raux.0.2.4"
!define RAUX_PRODUCT_NAME "GAIA UI"
!define RAUX_PRODUCT_SQUIRREL_NAME "GaiaUi"
!define PYTHON_VERSION "3.10.9"
!define PYTHON_EMBED_URL "https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip"
!define GET_PIP_URL "https://bootstrap.pypa.io/get-pip.py"

Key Functions

Function: .onInit

Function .onInit
  ; Initialize GAIA version string
  StrCpy $GAIA_STRING "GAIA ${GAIA_VERSION}"

  ; Set RAUX install to checked by default
  StrCpy $InstallRAUX "1"
FunctionEnd

Function: RAUXOptionsPage

Function RAUXOptionsPage
  ; Create custom page for RAUX selection
  !insertmacro MUI_HEADER_TEXT "Additional Components" "Choose additional components to install"
  nsDialogs::Create 1018
  Pop $0

  ; Create checkbox for RAUX
  ${NSD_CreateCheckbox} 10 10 100% 12u "Install ${RAUX_PRODUCT_NAME}"
  Pop $1
  ${NSD_SetState} $1 $InstallRAUX

  ; Show description
  ${NSD_CreateLabel} 25 30 100% 40u "RAUX UI provides a chat interface..."
  Pop $2

  nsDialogs::Show
FunctionEnd

Function: check_version_compatibility

; Check Lemonade version using Python script
nsExec::ExecToStack 'cmd /c ""$INSTDIR\python\python.exe" "$INSTDIR\installer_utils.py" "${LEMONADE_VERSION}""'
Pop $4  ; Return code (0=compatible, 1=not compatible)
Pop $5  ; Output with version info

; Extract version from output
${StrLoc} $6 "$5" "VERSION:" ">"
${If} $6 != ""
    IntOp $6 $6 + 8
    StrCpy $7 "$5" "" $6  ; Version string
${EndIf}

Implementation Details

Directory Cleanup Logic

Problem: Existing installation must be removed before new install. Solution:
; Check if directory exists
IfFileExists "$INSTDIR\*.*" 0 continue_install

    ; Interactive mode: Ask user
    ${IfNot} ${Silent}
        MessageBox MB_YESNO "An existing GAIA installation was found. Remove it?" IDYES remove_dir
        MessageBox MB_OK "Installation cancelled."
        Quit
    ${EndIf}

remove_dir:
    ; Remove all files
    RMDir /r "$INSTDIR"

    ; Verify removal
    IfFileExists "$INSTDIR\*.*" 0 continue_install
        MessageBox MB_OK "Unable to remove existing installation."
        Quit

continue_install:
    ; Proceed with fresh install

Python Configuration

Embedded Python Limitation: Does not include site-packages by default. Fix: Modify python310._pth file:
; Add site-packages to path
FileOpen $2 "$INSTDIR\python\python310._pth" a
FileSeek $2 0 END
FileWrite $2 "$\r$\nLib$\r$\n"
FileWrite $2 "$\r$\nLib\site-packages$\r$\n"
FileClose $2

Docopt Circular Dependency Workaround

Problem: Docopt has circular import that breaks pip install. Solution: Pre-download docopt.py:
DetailPrint "- Downloading docopt.py to fix circular dependency..."
nsExec::ExecToStack 'curl -L -o "$INSTDIR\python\docopt.py" "https://raw.githubusercontent.com/docopt/docopt/master/docopt.py"'
Pop $0  ; Return value
Pop $1  ; Output

${If} $0 != 0
    DetailPrint "- ERROR: Failed to download docopt.py"
    Quit
${EndIf}

PATH Environment Update

Requirements:
  • Update user PATH (not system)
  • Append, don’t replace
  • Avoid duplicates
  • Notify shell of change
Implementation:
; Read current PATH
ReadRegStr $0 HKCU "Environment" "PATH"

; Directories to add
StrCpy $1 "$INSTDIR\bin"
StrCpy $2 "$INSTDIR\python\Scripts"

; Check if already in PATH
${StrLoc} $3 "$0" "$1" ">"
${StrLoc} $4 "$0" "$2" ">"

; Only update if not present
${If} $3 == ""
${OrIf} $4 == ""
    ; Append to PATH
    WriteRegExpandStr HKCU "Environment" "PATH" "$0;$1;$2"

    ; Notify system of change
    SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
${EndIf}

Lemonade Version Checking

Flow:
  1. Check if lemonade-server is in PATH
  2. If found, check version compatibility
  3. If incompatible or missing, prompt to install
  4. Download from GitHub releases
  5. Run MSI with silent flag
Implementation:
check_lemonade:
    ; Try to run lemonade-server --version
    nsExec::ExecToStack 'cmd /c "lemonade-server --version 2>&1"'
    Pop $2  ; Return code
    Pop $3  ; Output

    ; If not found (code != 0)
    ${If} $2 != "0"
        MessageBox MB_YESNO "Lemonade is required. Install now?" IDYES install_lemonade
        GoTo skip_lemonade
    ${EndIf}

    ; Check version with Python script
    nsExec::ExecToStack '... installer_utils.py ...'
    Pop $4  ; 0=compatible, 1=incompatible

    ${If} $4 == "0"
        ; Version OK
        GoTo create_env
    ${Else}
        ; Version incompatible
        MessageBox MB_YESNO "Lemonade version mismatch. Update?" IDYES install_lemonade
    ${EndIf}

install_lemonade:
    ; Download MSI
    nsExec::ExecToStack 'curl -L -o "$TEMP\lemonade.msi" "$LEMONADE_URL"'

    ; Install silently
    ExecWait 'msiexec /i "$TEMP\lemonade.msi" /qn' $2

    ${If} $2 == "0"
        MessageBox MB_OK "Lemonade installed successfully"
    ${Else}
        MessageBox MB_OK "Lemonade install failed (exit code $2)"
    ${EndIf}

Testing Requirements

Build Tests

# Test NSIS compilation
makensis /V4 installer\Installer.nsi

# Check output exists
if (!(Test-Path "gaia-windows-setup.exe")) {
    throw "Build failed: installer not created"
}

# Verify file size (should be reasonable)
$size = (Get-Item "gaia-windows-setup.exe").Length / 1MB
if ($size -lt 5 -or $size -gt 100) {
    throw "Build warning: Unexpected installer size ${size}MB"
}

Installation Tests

# Test 1: Default install
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\GAIA" -ErrorAction SilentlyContinue
.\gaia-windows-setup.exe /S
Start-Sleep -Seconds 60  # Wait for install

# Verify
if (!(Test-Path "$env:LOCALAPPDATA\GAIA\python\python.exe")) {
    throw "Install failed: Python not found"
}

# Test 2: Custom path
.\gaia-windows-setup.exe /S /D=C:\Test\GAIA
Start-Sleep -Seconds 60

if (!(Test-Path "C:\Test\GAIA\python\python.exe")) {
    throw "Custom path install failed"
}

# Test 3: Upgrade
.\gaia-windows-setup-v1.0.0.exe /S
.\gaia-windows-setup-v1.1.0.exe /S

# Verify version
$version = & "$env:LOCALAPPDATA\GAIA\python\Scripts\gaia.exe" --version
if ($version -notmatch "1.1.0") {
    throw "Upgrade failed: version mismatch"
}

Regression Tests

# Test PATH update
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notlike "*GAIA\bin*") {
    throw "PATH not updated correctly"
}

# Test shortcuts
if (!(Test-Path "$env:USERPROFILE\Desktop\GAIA CLI.lnk")) {
    throw "Desktop shortcut not created"
}

# Test GAIA command
try {
    gaia --help | Out-Null
} catch {
    throw "GAIA command not in PATH"
}

# Test Python environment
$output = & "$env:LOCALAPPDATA\GAIA\python\python.exe" -c "import gaia; print(gaia.__version__)"
if ([string]::IsNullOrEmpty($output)) {
    throw "GAIA package not properly installed"
}

Dependencies

Build Dependencies

[build-system]
requires = [
    "nsis>=3.08",  # NSIS compiler
    "git",  # Version control
]

External Resources

runtime_downloads:
  - name: Python Embedded
    url: https://www.python.org/ftp/python/3.10.9/python-3.10.9-embed-amd64.zip
    size: ~9MB
    required: true

  - name: get-pip.py
    url: https://bootstrap.pypa.io/get-pip.py
    size: ~2MB
    required: true

  - name: docopt.py
    url: https://raw.githubusercontent.com/docopt/docopt/master/docopt.py
    size: ~20KB
    required: true

  - name: Lemonade Server
    url: https://github.com/lemonade-sdk/lemonade/releases/download/v8.0.4/lemonade-server-minimal.msi
    size: ~500MB
    required: false
    prompt: true

  - name: RAUX Setup
    url: https://github.com/aigdat/raux/releases/download/v0.6.5+raux.0.2.4/raux-setup.exe
    size: ~100MB
    required: false
    optional: true

Error Handling

Common Errors and Solutions

Error: “Failed to download Python”

${If} $0 != 0
    DetailPrint "- ERROR: Failed to download Python"
    ${IfNot} ${Silent}
        MessageBox MB_OK "Failed to download Python. Check internet connection and try again."
    ${EndIf}
    Quit
${EndIf}
User Action: Check network, retry install, or download Python manually.

Error: “GAIA installation failed”

REM install.bat
if %ERRORLEVEL% NEQ 0 (
    echo *** INSTALLATION FAILED ***
    echo Please check gaia_install.log for details
    exit /b 3
)
User Action: Check gaia_install.log, verify disk space, check antivirus.

Error: “PATH update failed”

${If} ${Errors}
    DetailPrint "- ERROR: Failed to write PATH to registry"
    MessageBox MB_OKCANCEL "Failed to update PATH. Continue anyway?" IDOK continue IDCANCEL abort
abort:
    Abort "Installation aborted"
continue:
    DetailPrint "- Continuing without PATH update"
${EndIf}
User Action: Manually add directories to PATH, or run installer as admin.

Configuration

Build Configuration

File: installer/version.nsh
!define GAIA_VERSION "1.0.0"
!define LEMONADE_VERSION "8.0.4"
Build Command:
# Set version
$version = "1.2.3"
"!define GAIA_VERSION `"$version`"" | Out-File -Encoding ASCII installer\version.nsh

# Compile
makensis /V4 installer\Installer.nsi

Runtime Configuration

GAIA uses:
  • Installation directory: $INSTDIR (default: $LOCALAPPDATA\GAIA)
  • Python exe: $INSTDIR\python\python.exe
  • Scripts dir: $INSTDIR\python\Scripts
  • Bin dir: $INSTDIR\bin
User can modify:
  • Install location via /D= flag
  • RAUX installation via checkbox
  • Lemonade installation via prompt

Usage Examples

Example 1: Automated Build Pipeline

# CI/CD build script
param(
    [Parameter(Mandatory=$true)]
    [string]$Version
)

# Create version file
@"
!define GAIA_VERSION "$Version"
!define LEMONADE_VERSION "8.0.4"
"@ | Out-File -Encoding ASCII installer\version.nsh

# Build installer
& "C:\Program Files (x86)\NSIS\makensis.exe" /V4 installer\Installer.nsi

# Check result
if ($LASTEXITCODE -ne 0) {
    throw "NSIS build failed"
}

# Rename with version
Move-Item gaia-windows-setup.exe "gaia-windows-setup-v$Version.exe" -Force

Write-Host "✓ Built installer: gaia-windows-setup-v$Version.exe"

Example 2: Enterprise Deployment

# Deploy to 100 machines
$computers = Get-ADComputer -Filter * -SearchBase "OU=Engineering,DC=company,DC=com"

foreach ($computer in $computers) {
    Invoke-Command -ComputerName $computer.Name -ScriptBlock {
        # Download installer
        $url = "https://releases.company.com/gaia-setup.exe"
        $setup = "$env:TEMP\gaia-setup.exe"
        Invoke-WebRequest -Uri $url -OutFile $setup

        # Install silently
        Start-Process -Wait -FilePath $setup -ArgumentList "/S"

        # Verify
        if (Test-Path "$env:LOCALAPPDATA\GAIA\python\Scripts\gaia.exe") {
            Write-Host "✓ Installed on $env:COMPUTERNAME"
        } else {
            Write-Warning "✗ Installation failed on $env:COMPUTERNAME"
        }

        # Cleanup
        Remove-Item $setup
    }
}

Example 3: Unattended Install with Logging

# Install with detailed logging
$logFile = "C:\Logs\gaia-install.log"

# Run installer
Start-Process -Wait -FilePath "gaia-windows-setup.exe" `
    -ArgumentList "/S /D=$env:ProgramData\GAIA" `
    -RedirectStandardOutput $logFile `
    -RedirectStandardError "${logFile}.err"

# Check log for errors
$errors = Select-String -Path "${logFile}.err" -Pattern "ERROR"
if ($errors) {
    Write-Error "Installation errors detected:"
    $errors | ForEach-Object { Write-Error $_.Line }
    exit 1
}

Write-Host "✓ Installation successful"

Acceptance Criteria

  • Installer compiles without warnings
  • File size under 100MB (excluding external downloads)
  • Installs to user directory (no admin required)
  • Silent mode works (/S flag)
  • Custom paths supported (/D= flag)
  • Existing installations detected and removed
  • Python embedded correctly with site-packages
  • Pip installs successfully
  • GAIA package installs with all extras
  • Lemonade version checking works
  • PATH updated correctly
  • Desktop shortcuts created
  • Launch options work on finish page
  • RAUX installer launches asynchronously
  • Detailed progress shown via DetailPrint
  • Error messages are actionable
  • Installation log created (gaia_install.log)
  • Uninstaller removes all files
  • Registry entries cleaned up on uninstall


NSIS Windows Installer Technical Specification