Add Linux and Windows installers
- install.sh: checks Python 3.12+, installs the app into its own environment, checks Qt's system libraries, installs icons and a --background .desktop launcher, offers start at login; --uninstall. - install.bat / install_windows.ps1: checks Python (offering winget), builds with PyInstaller, installs to LOCALAPPDATA with Start Menu and Desktop shortcuts, offers start at login; -Uninstall. - .gitattributes keeps .bat/.ps1 as CRLF and .sh as LF. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fb6220155c
commit
f16a67e042
5 files changed
+441
No files matched your search
@@ -0,0 +1,205 @@
|
||||
# MTGA Assistant installer for Windows.
|
||||
#
|
||||
# Normally started by double-clicking install.bat in the project folder. It:
|
||||
# 1. finds Python 3.12+ (offering to install it with winget if missing),
|
||||
# 2. installs the app's dependencies and PyInstaller into a private build environment,
|
||||
# 3. builds the app with PyInstaller,
|
||||
# 4. installs it to %LOCALAPPDATA%\Programs\MTGA Assistant (no admin rights needed),
|
||||
# 5. adds Start Menu and Desktop shortcuts, optionally starts it at login, and launches it.
|
||||
#
|
||||
# Uninstall: install.bat -Uninstall
|
||||
|
||||
param(
|
||||
[switch]$Uninstall,
|
||||
[switch]$NoLaunch
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$AppName = "MTGA Assistant"
|
||||
$ExeName = "MTGA Assistant.exe"
|
||||
$RunValue = "mtga-assistant" # same value the app's own "Start at login" option uses
|
||||
$RunKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
$MinPython = [version]"3.12"
|
||||
$WingetId = "Python.Python.3.12"
|
||||
|
||||
$ProjectDir = Split-Path -Parent $PSScriptRoot
|
||||
$BuildVenv = Join-Path $ProjectDir ".venv-build"
|
||||
$DistDir = Join-Path $ProjectDir "dist\$AppName"
|
||||
$InstallDir = Join-Path $env:LOCALAPPDATA "Programs\$AppName"
|
||||
$InstalledExe = Join-Path $InstallDir $ExeName
|
||||
$StartMenuLnk = Join-Path ([Environment]::GetFolderPath("Programs")) "$AppName.lnk"
|
||||
$DesktopLnk = Join-Path ([Environment]::GetFolderPath("Desktop")) "$AppName.lnk"
|
||||
|
||||
function Step($text) { Write-Host ""; Write-Host "==> $text" -ForegroundColor Cyan }
|
||||
function Info($text) { Write-Host " $text" }
|
||||
function Fail($text) { Write-Host ""; Write-Host "ERROR: $text" -ForegroundColor Red; exit 1 }
|
||||
|
||||
function Ask-YesNo($question, [bool]$default = $true) {
|
||||
$hint = if ($default) { "[Y/n]" } else { "[y/N]" }
|
||||
$answer = Read-Host " $question $hint"
|
||||
if ([string]::IsNullOrWhiteSpace($answer)) { return $default }
|
||||
return $answer.Trim().ToLower().StartsWith("y")
|
||||
}
|
||||
|
||||
function Stop-RunningApp {
|
||||
$running = Get-Process -Name $AppName -ErrorAction SilentlyContinue
|
||||
if ($running) {
|
||||
Info "Closing the running $AppName..."
|
||||
$running | Stop-Process -Force
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-Shortcuts {
|
||||
foreach ($lnk in @($StartMenuLnk, $DesktopLnk)) {
|
||||
if (Test-Path $lnk) { Remove-Item $lnk -Force }
|
||||
}
|
||||
}
|
||||
|
||||
# --- uninstall -------------------------------------------------------------------------------
|
||||
|
||||
if ($Uninstall) {
|
||||
Step "Uninstalling $AppName"
|
||||
Stop-RunningApp
|
||||
Remove-Shortcuts
|
||||
Remove-ItemProperty -Path $RunKey -Name $RunValue -ErrorAction SilentlyContinue
|
||||
if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force }
|
||||
Info "Removed the app, its shortcuts and its login item."
|
||||
Info "Your game history and settings were kept in $env:LOCALAPPDATA\mtga-assistant and $env:APPDATA\mtga-assistant."
|
||||
Info "Delete those folders too if you want to remove everything."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "$AppName installer" -ForegroundColor Green
|
||||
Info "Project folder: $ProjectDir"
|
||||
|
||||
# --- 1. Python --------------------------------------------------------------------------------
|
||||
|
||||
function Refresh-Path {
|
||||
# winget installs update PATH in the registry, not in this already-running session.
|
||||
$machine = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
$user = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$env:Path = "$machine;$user"
|
||||
}
|
||||
|
||||
function Test-Python([string[]]$command) {
|
||||
# Returns the version if this command runs a real Python >= $MinPython, else $null.
|
||||
# (The Microsoft Store "python" alias exists but only opens the Store, so it fails here.)
|
||||
try {
|
||||
$exe = $command[0]
|
||||
$prefix = @($command | Select-Object -Skip 1)
|
||||
$out = & $exe @prefix -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or -not $out) { return $null }
|
||||
$version = [version]($out | Select-Object -First 1).Trim()
|
||||
if ($version -ge $MinPython) { return $version }
|
||||
} catch { }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-Python {
|
||||
$candidates = @(
|
||||
@("py", "-3.13"), @("py", "-3.12"), @("py", "-3"),
|
||||
@("python"), @("python3"),
|
||||
@((Join-Path $env:LOCALAPPDATA "Programs\Python\Python313\python.exe")),
|
||||
@((Join-Path $env:LOCALAPPDATA "Programs\Python\Python312\python.exe"))
|
||||
)
|
||||
foreach ($cmd in $candidates) {
|
||||
$cmd = [string[]]@($cmd) # keep one-word commands as arrays, not strings
|
||||
$version = Test-Python $cmd
|
||||
if ($version) { return @{ Command = $cmd; Version = $version } }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
Step "Checking for Python $MinPython or newer"
|
||||
$python = Find-Python
|
||||
if (-not $python) {
|
||||
Info "Python $MinPython+ was not found."
|
||||
$winget = Get-Command winget -ErrorAction SilentlyContinue
|
||||
if ($winget -and (Ask-YesNo "Install Python 3.12 now with winget (for your user only)?")) {
|
||||
& winget install --exact --id $WingetId --scope user --silent `
|
||||
--accept-package-agreements --accept-source-agreements
|
||||
if ($LASTEXITCODE -ne 0) { Fail "winget couldn't install Python (exit code $LASTEXITCODE)." }
|
||||
Refresh-Path
|
||||
$python = Find-Python
|
||||
}
|
||||
if (-not $python) {
|
||||
Info "Install Python from https://www.python.org/downloads/ (tick 'Add python.exe to PATH'),"
|
||||
Info "then run install.bat again."
|
||||
Start-Process "https://www.python.org/downloads/windows/"
|
||||
Fail "Python $MinPython or newer is required."
|
||||
}
|
||||
}
|
||||
$pyExe = $python.Command[0]
|
||||
$pyArgs = @($python.Command | Select-Object -Skip 1)
|
||||
Info "Using Python $($python.Version) ($($python.Command -join ' '))"
|
||||
|
||||
# --- 2. Dependencies --------------------------------------------------------------------------
|
||||
|
||||
Step "Installing dependencies (first run downloads ~100 MB; this can take a few minutes)"
|
||||
$venvPython = Join-Path $BuildVenv "Scripts\python.exe"
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
& $pyExe @pyArgs -m venv $BuildVenv
|
||||
if ($LASTEXITCODE -ne 0) { Fail "Couldn't create the build environment in $BuildVenv." }
|
||||
}
|
||||
& $venvPython -m pip install --disable-pip-version-check --quiet --upgrade pip
|
||||
& $venvPython -m pip install --disable-pip-version-check --quiet $ProjectDir "pyinstaller>=6"
|
||||
if ($LASTEXITCODE -ne 0) { Fail "Installing dependencies failed (see the messages above)." }
|
||||
Info "Dependencies installed."
|
||||
|
||||
# --- 3. Build ------------------------------------------------------------------------------------
|
||||
|
||||
Step "Building $AppName"
|
||||
Push-Location $ProjectDir
|
||||
try {
|
||||
& $venvPython -m PyInstaller --noconfirm --clean --log-level WARN "packaging\mtga-assistant.spec"
|
||||
if ($LASTEXITCODE -ne 0) { Fail "The PyInstaller build failed (see the messages above)." }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $DistDir $ExeName))) { Fail "The build finished but $ExeName is missing." }
|
||||
Info "Built $DistDir"
|
||||
|
||||
# --- 4. Install ----------------------------------------------------------------------------------
|
||||
|
||||
Step "Installing to $InstallDir"
|
||||
Stop-RunningApp
|
||||
if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path (Split-Path $InstallDir) -Force | Out-Null
|
||||
Copy-Item -Path $DistDir -Destination $InstallDir -Recurse
|
||||
Info "Installed."
|
||||
|
||||
# --- 5. Shortcuts, login item, launch -------------------------------------------------------------
|
||||
|
||||
Step "Creating shortcuts"
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
foreach ($lnkPath in @($StartMenuLnk, $DesktopLnk)) {
|
||||
$lnk = $shell.CreateShortcut($lnkPath)
|
||||
$lnk.TargetPath = $InstalledExe
|
||||
$lnk.WorkingDirectory = $InstallDir
|
||||
$lnk.IconLocation = "$InstalledExe,0"
|
||||
$lnk.Description = "Deck and game tracker for Magic: The Gathering Arena"
|
||||
$lnk.Save()
|
||||
}
|
||||
Info "Start Menu: $StartMenuLnk"
|
||||
Info "Desktop: $DesktopLnk"
|
||||
|
||||
Step "Start at login"
|
||||
Info "Running in the background lets it record every game and pop up when MTGA starts."
|
||||
if (Ask-YesNo "Start $AppName hidden when you log in?") {
|
||||
Set-ItemProperty -Path $RunKey -Name $RunValue -Value "`"$InstalledExe`" --background"
|
||||
Info "Added. (You can change this later in the app: menu -> Start at login.)"
|
||||
} else {
|
||||
Remove-ItemProperty -Path $RunKey -Name $RunValue -ErrorAction SilentlyContinue
|
||||
Info "Skipped."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "$AppName is installed." -ForegroundColor Green
|
||||
Info "Remember to enable MTGA's detailed logs: Options -> Account -> Detailed Logs (Plugin Support)."
|
||||
Info "To uninstall later, run: install.bat -Uninstall"
|
||||
if (-not $NoLaunch) {
|
||||
Start-Process -FilePath $InstalledExe -WorkingDirectory $InstallDir
|
||||
}
|
||||
Reference in new issue
Block a user