Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Transform isair/jarvis into a Discord-controlled voice assistant running on the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact. - bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral), voice channel join + voice receive/playback, pluggable VNC screen broadcast (selfbot live / noVNC / screenshot) - bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS behind a thin localhost HTTP API - .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite, docs/language-comparison.md and docs/vnc-xfce-setup.md Language decision: hybrid (Python brain + Node/bun Discord layer) because Discord blocks bot video; native screen broadcast only works via a Node selfbot library.
This commit is contained in:
284
installer/windows/install_cuda.ps1
Normal file
284
installer/windows/install_cuda.ps1
Normal file
@@ -0,0 +1,284 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Download and install CUDA libraries for GPU-accelerated speech recognition.
|
||||
|
||||
.DESCRIPTION
|
||||
Downloads NVIDIA cuBLAS and cuDNN libraries from PyPI wheel packages
|
||||
and extracts the DLLs into the target directory. Wheels are just ZIP
|
||||
files, so no Python is needed.
|
||||
|
||||
The script is intended to be safe to re-run: a stale marker file from
|
||||
a previous half-successful install does not cause us to skip work.
|
||||
Every run probes for the expected DLLs first, downloads what's
|
||||
missing, verifies SHA256 against the digest PyPI returns, verifies
|
||||
that every expected DLL ended up on disk, and only then writes the
|
||||
marker. Output is also written to a transcript log so failures from
|
||||
Inno Setup's hidden invocation are recoverable.
|
||||
|
||||
Invoked by the Inno Setup installer when the user opts into GPU
|
||||
acceleration, by the tray-menu recovery action, or manually:
|
||||
powershell -ExecutionPolicy Bypass -File install_cuda.ps1 `
|
||||
-TargetDir "C:\Program Files\Jarvis\cuda"
|
||||
|
||||
.PARAMETER TargetDir
|
||||
Directory to extract CUDA DLLs into (e.g. {app}\cuda).
|
||||
|
||||
.PARAMETER LogPath
|
||||
Optional path for the transcript log. Defaults to {TargetDir}\install.log.
|
||||
|
||||
.PARAMETER PyPIIndexUrl
|
||||
Base URL for the PyPI JSON API. Override for testing only.
|
||||
|
||||
.PARAMETER SkipGpuCheck
|
||||
Skip the local nvcuda.dll check. Used by tests; never set in production.
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$TargetDir,
|
||||
|
||||
[string]$LogPath,
|
||||
|
||||
[string]$PyPIIndexUrl = "https://pypi.org/pypi",
|
||||
|
||||
[switch]$SkipGpuCheck
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
# Suppress the progress bar before any Invoke-WebRequest call. With the
|
||||
# default 'Continue' preference, PowerShell repaints the progress UI on
|
||||
# every byte, which slows large downloads by 5–10x; the 643 MB cuDNN
|
||||
# wheel goes from ~3 minutes to half an hour on common connections.
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Package manifest
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pinned versions known to work with CTranslate2 4.x (CUDA 12, cuDNN 9).
|
||||
# `ExpectedDlls` is the list we verify on disk after extraction; if any are
|
||||
# missing or suspiciously small the install fails loudly instead of leaving
|
||||
# a stale marker behind.
|
||||
$packages = @(
|
||||
@{
|
||||
Name = "nvidia-cublas-cu12"
|
||||
Version = "12.9.1.4"
|
||||
Wheel = "nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl"
|
||||
Prefix = "nvidia/cublas/bin/"
|
||||
ExpectedDlls = @(
|
||||
"cublas64_12.dll",
|
||||
"cublasLt64_12.dll",
|
||||
"nvblas64_12.dll"
|
||||
)
|
||||
},
|
||||
@{
|
||||
Name = "nvidia-cudnn-cu12"
|
||||
Version = "9.20.0.48"
|
||||
Wheel = "nvidia_cudnn_cu12-9.20.0.48-py3-none-win_amd64.whl"
|
||||
Prefix = "nvidia/cudnn/bin/"
|
||||
ExpectedDlls = @(
|
||||
"cudnn64_9.dll",
|
||||
"cudnn_adv64_9.dll",
|
||||
"cudnn_cnn64_9.dll",
|
||||
"cudnn_engines_precompiled64_9.dll",
|
||||
"cudnn_engines_runtime_compiled64_9.dll",
|
||||
"cudnn_graph64_9.dll",
|
||||
"cudnn_heuristic64_9.dll",
|
||||
"cudnn_ops64_9.dll"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
# Minimum reasonable size for a CUDA DLL. The smallest real cuDNN file is
|
||||
# ~260 KB (`cudnn64_9.dll`); anything below this is almost certainly a
|
||||
# truncated download or an AV stub. Catch this case explicitly so we don't
|
||||
# write a marker for a corrupt install.
|
||||
$MIN_DLL_BYTES = 4096
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
function Get-AllExpectedDlls {
|
||||
$names = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($pkg in $packages) {
|
||||
foreach ($dll in $pkg.ExpectedDlls) {
|
||||
$names.Add($dll) | Out-Null
|
||||
}
|
||||
}
|
||||
return ,$names.ToArray()
|
||||
}
|
||||
|
||||
function Test-InstalledDlls {
|
||||
param([string]$Dir)
|
||||
|
||||
$missing = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($name in (Get-AllExpectedDlls)) {
|
||||
$path = Join-Path $Dir $name
|
||||
if (-not (Test-Path $path)) {
|
||||
$missing.Add($name) | Out-Null
|
||||
continue
|
||||
}
|
||||
$size = (Get-Item $path).Length
|
||||
if ($size -lt $MIN_DLL_BYTES) {
|
||||
$missing.Add("$name (truncated: $size bytes)") | Out-Null
|
||||
}
|
||||
}
|
||||
return ,$missing.ToArray()
|
||||
}
|
||||
|
||||
function Get-WheelInfo {
|
||||
param([string]$PackageName, [string]$Version, [string]$WheelFilename)
|
||||
|
||||
$url = "$PyPIIndexUrl/$PackageName/$Version/json"
|
||||
$resp = Invoke-RestMethod -Uri $url -UseBasicParsing -TimeoutSec 60
|
||||
foreach ($file in $resp.urls) {
|
||||
if ($file.filename -eq $WheelFilename) {
|
||||
$sha256 = $null
|
||||
if ($file.digests -and $file.digests.sha256) {
|
||||
$sha256 = $file.digests.sha256
|
||||
}
|
||||
return @{ Url = $file.url; Sha256 = $sha256 }
|
||||
}
|
||||
}
|
||||
throw "Wheel $WheelFilename not found on PyPI for $PackageName==$Version"
|
||||
}
|
||||
|
||||
function Test-FileSha256 {
|
||||
param([string]$Path, [string]$Expected)
|
||||
|
||||
if ([string]::IsNullOrEmpty($Expected)) {
|
||||
# PyPI always returns digests for hosted wheels; if it didn't, fail
|
||||
# loudly rather than silently skip the integrity check.
|
||||
throw "PyPI did not return a SHA256 digest for $Path"
|
||||
}
|
||||
$actual = (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToLower()
|
||||
if ($actual -ne $Expected.ToLower()) {
|
||||
throw "SHA256 mismatch for $Path (expected $Expected, got $actual)"
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Begin install
|
||||
# ---------------------------------------------------------------------------
|
||||
New-Item -ItemType Directory -Force -Path $TargetDir | Out-Null
|
||||
|
||||
if (-not $LogPath) {
|
||||
$LogPath = Join-Path $TargetDir "install.log"
|
||||
}
|
||||
|
||||
# Ensure log directory exists, then start a transcript so every line — Write-Host,
|
||||
# Write-Error, exceptions — lands in the file. The Inno Setup invocation runs
|
||||
# hidden, so without this a failure is invisible to the user.
|
||||
$logDir = Split-Path -Parent $LogPath
|
||||
if ($logDir) { New-Item -ItemType Directory -Force -Path $logDir | Out-Null }
|
||||
try {
|
||||
Start-Transcript -Path $LogPath -Force | Out-Null
|
||||
$transcriptStarted = $true
|
||||
} catch {
|
||||
$transcriptStarted = $false
|
||||
}
|
||||
|
||||
$marker = Join-Path $TargetDir ".cuda_installed"
|
||||
|
||||
try {
|
||||
# --- Pre-flight: NVIDIA GPU driver detection ---
|
||||
if (-not $SkipGpuCheck) {
|
||||
$nvcudaPaths = @(
|
||||
(Join-Path $env:SystemRoot "System32\nvcuda.dll"),
|
||||
(Join-Path $env:windir "System32\nvcuda.dll")
|
||||
)
|
||||
$gpuFound = $false
|
||||
foreach ($p in $nvcudaPaths) {
|
||||
if (Test-Path $p) { $gpuFound = $true; break }
|
||||
}
|
||||
if (-not $gpuFound) {
|
||||
Write-Host "No NVIDIA GPU detected, skipping CUDA installation."
|
||||
return # exit 0; no GPU is not a failure
|
||||
}
|
||||
}
|
||||
|
||||
# --- Idempotence: skip only if every expected DLL is actually on disk ---
|
||||
$missing = Test-InstalledDlls -Dir $TargetDir
|
||||
if ((Test-Path $marker) -and $missing.Length -eq 0) {
|
||||
Write-Host "CUDA libraries already installed and verified."
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-Path $marker) {
|
||||
Write-Host "Stale marker found but DLLs missing/truncated; reinstalling..."
|
||||
Write-Host " Missing: $($missing -join ', ')"
|
||||
# Remove the marker up-front so a crash mid-install can't leave a
|
||||
# falsely-green state.
|
||||
Remove-Item -Force $marker -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Downloading CUDA libraries for GPU acceleration..."
|
||||
Write-Host "Target: $TargetDir"
|
||||
Write-Host "Log: $LogPath"
|
||||
|
||||
foreach ($pkg in $packages) {
|
||||
Write-Host ""
|
||||
Write-Host "Downloading $($pkg.Name) $($pkg.Version)..."
|
||||
|
||||
$info = Get-WheelInfo `
|
||||
-PackageName $pkg.Name `
|
||||
-Version $pkg.Version `
|
||||
-WheelFilename $pkg.Wheel
|
||||
|
||||
$tmpFile = [System.IO.Path]::GetTempFileName() + ".whl"
|
||||
|
||||
try {
|
||||
# Use Invoke-WebRequest: it's slower than WebClient on some
|
||||
# systems but it raises on truncation rather than silently
|
||||
# writing a partial file, which is the documented WebClient
|
||||
# failure mode that motivated this rewrite.
|
||||
Invoke-WebRequest -Uri $info.Url -OutFile $tmpFile -UseBasicParsing -TimeoutSec 600
|
||||
Write-Host " Download complete."
|
||||
|
||||
Test-FileSha256 -Path $tmpFile -Expected $info.Sha256
|
||||
Write-Host " SHA256 verified."
|
||||
|
||||
Write-Host " Extracting DLLs..."
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmpFile)
|
||||
try {
|
||||
foreach ($entry in $zip.Entries) {
|
||||
if ($entry.FullName.StartsWith($pkg.Prefix) -and $entry.FullName.EndsWith(".dll")) {
|
||||
$destPath = Join-Path $TargetDir $entry.Name
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destPath, $true)
|
||||
Write-Host " $($entry.Name)"
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$zip.Dispose()
|
||||
}
|
||||
} finally {
|
||||
if (Test-Path $tmpFile) {
|
||||
Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Post-extract verification ---
|
||||
$missingAfter = Test-InstalledDlls -Dir $TargetDir
|
||||
if ($missingAfter.Length -gt 0) {
|
||||
throw "Verification failed after extract; missing/truncated: $($missingAfter -join ', ')"
|
||||
}
|
||||
|
||||
# --- Marker is the LAST thing written ---
|
||||
$markerContent = $packages | ForEach-Object { "$($_.Name)==$($_.Version)" }
|
||||
$markerContent | Out-File -FilePath $marker -Encoding utf8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "CUDA libraries installed successfully!"
|
||||
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "CUDA installation FAILED: $_"
|
||||
Write-Host "See transcript at $LogPath"
|
||||
if ($transcriptStarted) { Stop-Transcript | Out-Null }
|
||||
exit 1
|
||||
} finally {
|
||||
if ($transcriptStarted) {
|
||||
try { Stop-Transcript | Out-Null } catch { }
|
||||
}
|
||||
}
|
||||
150
installer/windows/jarvis_setup.iss
Normal file
150
installer/windows/jarvis_setup.iss
Normal file
@@ -0,0 +1,150 @@
|
||||
; Jarvis Inno Setup Script
|
||||
; Builds a Windows installer from the PyInstaller onedir output.
|
||||
;
|
||||
; Usage:
|
||||
; iscc installer\windows\jarvis_setup.iss
|
||||
;
|
||||
; Expects the PyInstaller onedir output at dist\Jarvis\
|
||||
|
||||
#define MyAppName "Jarvis"
|
||||
#define MyAppExeName "Jarvis.exe"
|
||||
#define MyAppPublisher ""
|
||||
; Version can be overridden via ISCC command line: /DMyAppVersion=1.2.3
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "0.0.0"
|
||||
#endif
|
||||
|
||||
; VC++ Redistributable download URL (VS 2015-2022 x64)
|
||||
#define VCRedistURL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
|
||||
[Setup]
|
||||
AppId={{B8A3D6F1-7C42-4E5A-9D12-3F8E6A1B5C90}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
OutputDir=..\..\dist
|
||||
OutputBaseFilename=Jarvis-Setup-x64
|
||||
Compression=lzma2
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
ArchitecturesAllowed=x64compatible
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
PrivilegesRequired=admin
|
||||
SetupIconFile=..\..\src\desktop_app\desktop_assets\icon_idle.ico
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "cudalibs"; Description: "Download NVIDIA CUDA libraries for GPU-accelerated speech recognition (~1.1 GB download)"; GroupDescription: "GPU Acceleration:"; Check: HasNvidiaGPU; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
; Bundle the entire PyInstaller onedir output
|
||||
Source: "..\..\dist\Jarvis\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; Bundle the CUDA installer script (PowerShell — no Python needed)
|
||||
Source: "install_cuda.ps1"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{group}\Uninstall {#MyAppName}"; Filename: "{uninstallexe}"
|
||||
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
; Install VC++ Redistributable silently if missing
|
||||
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/quiet /norestart"; StatusMsg: "Installing Visual C++ Redistributable..."; Flags: waituntilterminated; Check: VCRedistNeeded
|
||||
; Download CUDA libraries if task selected (uses PowerShell to download and extract wheels).
|
||||
; -LogPath ensures every run leaves a transcript at {app}\cuda\install.log so a hidden
|
||||
; failure here is recoverable from the bug-report flow and the tray "Reinstall GPU libraries" action.
|
||||
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\install_cuda.ps1"" -TargetDir ""{app}\cuda"" -LogPath ""{app}\cuda\install.log"""; StatusMsg: "Downloading CUDA libraries for GPU acceleration (this may take several minutes)..."; Flags: waituntilterminated runhidden; Tasks: cudalibs; AfterInstall: VerifyCudaInstall
|
||||
; Launch the application after installation
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "Launch {#MyAppName}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{app}"
|
||||
|
||||
[Code]
|
||||
// Check whether the VC++ 2015-2022 runtime is already installed
|
||||
function VCRedistNeeded: Boolean;
|
||||
var
|
||||
Version: String;
|
||||
begin
|
||||
// Check for VC++ 2015-2022 x64 runtime via registry
|
||||
Result := True;
|
||||
if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', Version) then
|
||||
begin
|
||||
// Runtime is installed
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Check whether an NVIDIA GPU is present by looking for the CUDA driver DLL
|
||||
function HasNvidiaGPU: Boolean;
|
||||
var
|
||||
NvSmiPath: String;
|
||||
begin
|
||||
// nvcuda.dll is the CUDA driver — present on any system with NVIDIA drivers
|
||||
NvSmiPath := ExpandConstant('{sys}\nvcuda.dll');
|
||||
Result := FileExists(NvSmiPath);
|
||||
end;
|
||||
|
||||
// Surface CUDA install failures to the user instead of silently letting the
|
||||
// installer report success. install_cuda.ps1 only writes its marker after
|
||||
// verifying every expected DLL is on disk, so a missing marker means the
|
||||
// install really did fail and the user needs to know they can recover via
|
||||
// the tray menu's "Reinstall GPU libraries" action.
|
||||
procedure VerifyCudaInstall;
|
||||
var
|
||||
MarkerPath, LogPath: String;
|
||||
begin
|
||||
MarkerPath := ExpandConstant('{app}\cuda\.cuda_installed');
|
||||
LogPath := ExpandConstant('{app}\cuda\install.log');
|
||||
if not FileExists(MarkerPath) then
|
||||
begin
|
||||
Log('CUDA install marker not found at ' + MarkerPath + '; install failed.');
|
||||
MsgBox(
|
||||
'GPU library download did not complete. Jarvis will run on CPU.' #13#10 #13#10 +
|
||||
'You can retry later from the tray menu via "Reinstall GPU libraries".' #13#10 #13#10 +
|
||||
'Details: ' + LogPath,
|
||||
mbInformation, MB_OK);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Download VC++ Redistributable if needed
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssInstall then
|
||||
begin
|
||||
if VCRedistNeeded then
|
||||
begin
|
||||
// Download vc_redist.x64.exe from Microsoft
|
||||
DownloadTemporaryFile('{#VCRedistURL}', 'vc_redist.x64.exe', '', nil);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// After installation, clean up the old exe if the installer was launched
|
||||
// from a legacy location (e.g. old updater placed it at a custom path).
|
||||
// The installer can't delete itself while running, so we schedule a
|
||||
// cmd /c del command that retries until the file is unlocked.
|
||||
procedure DeinitializeSetup;
|
||||
var
|
||||
InstallerPath, InstalledDir: String;
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
InstallerPath := ExpandConstant('{srcexe}');
|
||||
InstalledDir := ExpandConstant('{app}');
|
||||
// Only clean up if the installer is NOT inside the installation directory
|
||||
// (i.e. it was placed somewhere else by the old updater)
|
||||
if Pos(Lowercase(InstalledDir), Lowercase(InstallerPath)) = 0 then
|
||||
begin
|
||||
Log('Scheduling cleanup of old installer at: ' + InstallerPath);
|
||||
Exec('cmd.exe',
|
||||
'/c ping -n 3 127.0.0.1 >nul & del /f "' + InstallerPath + '"',
|
||||
'', SW_HIDE, ewNoWait, ResultCode);
|
||||
end;
|
||||
end;
|
||||
Reference in New Issue
Block a user