#Requires -Version 5.1 <# .SYNOPSIS NOVA - Navigation, Operations, and Vessel Assistance Portable launcher for Windows — all data lives next to this script. .DESCRIPTION Auto-updates itself and NOVA from GitHub, then launches NOVA. Installs Python and NOVA automatically if not present. All data (config, database, venv) stays in the same directory as this script. .PARAMETER NoSelfUpdate Skip the self-update check (used internally after auto-update). .PARAMETER Uninstall Completely remove the NOVA directory. Does not touch Elite Dangerous journal files. .EXAMPLE .\nova.ps1 .\nova.ps1 -Uninstall #> param( [switch]$NoSelfUpdate, [switch]$Uninstall ) $ErrorActionPreference = "Stop" $NOVA_URL = "git+https://github.com/KernicDE/nova-ed-monitor.git" $NOVA_PKG = "nova-ed-monitor" $PYTHON_INSTALLER_URL = "https://www.python.org/ftp/python/3.12.7/python-3.12.7-amd64.exe" $SCRIPT_URL = "https://raw.githubusercontent.com/KernicDE/nova-ed-monitor/main/nova.ps1" $GH_API_URL = "https://api.github.com/repos/KernicDE/nova-ed-monitor/releases/latest" # Portable root = directory containing this script (all data lives here) $ScriptPath = $MyInvocation.MyCommand.Path $PortableRoot = Split-Path $ScriptPath -Parent $VenvDir = Join-Path $PortableRoot "venv" $VenvPip = Join-Path $VenvDir "Scripts\pip.exe" $VenvPython = Join-Path $VenvDir "Scripts\python.exe" $VenvNova = Join-Path $VenvDir "Scripts\nova.exe" function Write-Info { param($Msg) Write-Host " $Msg" -ForegroundColor Cyan } function Write-Success { param($Msg) Write-Host " $Msg" -ForegroundColor Green } function Write-Warn { param($Msg) Write-Host " $Msg" -ForegroundColor Yellow } function Write-Err { param($Msg) Write-Host " $Msg" -ForegroundColor Red } # -- Banner -------------------------------------------------------------------- Write-Host "" Write-Host " # # ### # # ## " -ForegroundColor Cyan Write-Host " ## # # # # # # # " -ForegroundColor Cyan Write-Host " # ## # # # # #### " -ForegroundColor Cyan Write-Host " # # # # # # # # " -ForegroundColor Cyan Write-Host " # # ### # # #" -ForegroundColor Cyan Write-Host "" Write-Host " Navigation, Operations, and Vessel Assistance" -ForegroundColor White Write-Host " ---------------------------------------------" -ForegroundColor DarkGray Write-Host "" # -- Uninstall ----------------------------------------------------------------- if ($Uninstall) { Write-Host "" Write-Warn "This will permanently remove the entire NOVA directory:" Write-Warn " $PortableRoot" Write-Host "" Write-Host " Elite Dangerous journal files will NOT be touched." -ForegroundColor Green Write-Host "" $answer = Read-Host " Confirm uninstall? [y/N]" if ($answer -eq "y" -or $answer -eq "Y") { Remove-Item $PortableRoot -Recurse -Force Write-Success "NOVA uninstalled." } else { Write-Host " Cancelled." -ForegroundColor DarkGray } exit 0 } # -- Self-update --------------------------------------------------------------- if (-not $NoSelfUpdate -and $ScriptPath) { try { $tmp = [System.IO.Path]::GetTempFileName() + ".ps1" Invoke-WebRequest -Uri $SCRIPT_URL -OutFile $tmp -UseBasicParsing -TimeoutSec 8 -ErrorAction Stop $oldHash = (Get-FileHash $ScriptPath -Algorithm SHA256).Hash $newHash = (Get-FileHash $tmp -Algorithm SHA256).Hash if ($oldHash -ne $newHash) { Write-Info "Script update found - applying..." Copy-Item $tmp $ScriptPath -Force Write-Success "Script updated. Restarting..." Write-Host "" & powershell.exe -ExecutionPolicy Bypass -File $ScriptPath -NoSelfUpdate exit } Remove-Item $tmp -Force -ErrorAction SilentlyContinue } catch { # No internet or download failed — continue without self-update } } # -- Find Python 3.11+ --------------------------------------------------------- function Get-Python { foreach ($cmd in @("python", "python3", "py")) { try { $r = & $cmd -c "import sys; print(sys.version_info >= (3,11))" 2>$null if ($LASTEXITCODE -eq 0 -and $r -eq "True") { return $cmd } } catch { } } return $null } function Refresh-Path { $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH","User") } $Python = Get-Python if (-not $Python) { Write-Warn "Python 3.11+ not found. Downloading Python 3.12.7..." Write-Host "" $installer = Join-Path $env:TEMP "python-installer.exe" try { Write-Info "Downloading from python.org..." Invoke-WebRequest -Uri $PYTHON_INSTALLER_URL -OutFile $installer -UseBasicParsing } catch { Write-Err "Download failed: $_" Write-Err "Please install Python 3.11+ manually from https://www.python.org/downloads/" Read-Host "Press Enter to exit"; exit 1 } Write-Info "Installing Python (this takes about a minute)..." $proc = Start-Process $installer ` -ArgumentList "/quiet InstallAllUsers=0 PrependPath=1 Include_test=0 Include_launcher=1" ` -Wait -PassThru Remove-Item $installer -Force -ErrorAction SilentlyContinue if ($proc.ExitCode -ne 0) { Write-Err "Python installer failed (code $($proc.ExitCode))." Read-Host "Press Enter to exit"; exit 1 } Refresh-Path $Python = Get-Python if (-not $Python) { Write-Err "Python installed but not in PATH. Please reopen this window." Read-Host "Press Enter to exit"; exit 1 } Write-Host "" } Write-Success "Python: $(& $Python --version 2>&1)" # -- Bootstrap: create portable layout + install NOVA on first run ------------- if (-not (Test-Path $VenvDir)) { Write-Info "First run — setting up NOVA in $PortableRoot ..." @("config","data","logs") | ForEach-Object { New-Item -ItemType Directory -Path (Join-Path $PortableRoot $_) -Force | Out-Null } Write-Info "Creating virtual environment..." & $Python -m venv $VenvDir # Fetch latest release info from GitHub $whlUrl = "" try { $rel = Invoke-RestMethod -Uri $GH_API_URL -TimeoutSec 15 -UseBasicParsing -ErrorAction Stop $whlAsset = $rel.assets | Where-Object { $_.name -like "*.whl" } | Select-Object -First 1 if ($whlAsset) { $whlUrl = $whlAsset.browser_download_url } if (-not $whlUrl -and $rel.tag_name) { $whlUrl = "https://github.com/KernicDE/nova-ed-monitor/archive/refs/tags/$($rel.tag_name).tar.gz" } } catch { } Write-Info "Installing NOVA (this takes a minute)..." & $VenvPython -m pip install --quiet --upgrade pip 2>&1 | Out-Null if ($whlUrl) { & $VenvPython -m pip install --quiet $whlUrl } else { & $VenvPython -m pip install --quiet $NOVA_URL } if ($LASTEXITCODE -ne 0) { Write-Err "Installation failed. Check your internet connection." Read-Host "Press Enter to exit"; exit 1 } Write-Success "NOVA installed successfully!" Write-Host "" } else { # -- Auto-update NOVA package ----------------------------------------------- # Upgrade pip via python -m pip (pip.exe cannot upgrade itself on Windows) & $VenvPython -m pip install --quiet --upgrade pip 2>&1 | Out-Null $latestVer = "" $whlUrl = "" try { $rel = Invoke-RestMethod -Uri $GH_API_URL -TimeoutSec 15 -UseBasicParsing -ErrorAction Stop $latestVer = $rel.tag_name.TrimStart("v") $whlAsset = $rel.assets | Where-Object { $_.name -like "*.whl" } | Select-Object -First 1 if ($whlAsset) { $whlUrl = $whlAsset.browser_download_url } if (-not $whlUrl -and $rel.tag_name) { $whlUrl = "https://github.com/KernicDE/nova-ed-monitor/archive/refs/tags/$($rel.tag_name).tar.gz" } } catch { } $pipShow = & $VenvPip show $NOVA_PKG 2>&1 $installedVer = ($pipShow | Select-String "^Version:").ToString().Split(" ")[1].Trim() if ($latestVer -and $installedVer -ne $latestVer) { Write-Info "Update available: $installedVer -> $latestVer - updating..." if ($whlUrl) { & $VenvPython -m pip install --quiet $whlUrl } else { & $VenvPython -m pip install --quiet --upgrade $NOVA_URL } Write-Success "NOVA updated to $latestVer." Write-Host "" } else { Write-Success "NOVA $installedVer is up to date." Write-Host "" } } # -- Launch NOVA --------------------------------------------------------------- Write-Info "Starting NOVA..." Write-Host "" $env:NOVA_PORTABLE_ROOT = $PortableRoot & $VenvNova