Skip to content

Commit 93d354d

Browse files
committed
Update installation
1 parent 1fe036e commit 93d354d

File tree

2 files changed

+356
-196
lines changed

2 files changed

+356
-196
lines changed

install.ps1

Lines changed: 152 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,243 @@
1-
# Vix.cpp installer (Windows PowerShell)
1+
# Vix.cpp installer (Windows PowerShell) + install.json
22
# Usage:
33
# irm https://vixcpp.com/install.ps1 | iex
44
# Optional:
55
# $env:VIX_VERSION="v1.20.1"
66
# $env:VIX_INSTALL_DIR="$env:LOCALAPPDATA\Vix\bin"
77
# $env:VIX_REPO="vixcpp/vix"
8+
# $env:VIX_STATS_FILE="$env:LOCALAPPDATA\Vix\install.json" # optional override
89

910
$ErrorActionPreference = "Stop"
1011
$ProgressPreference = "SilentlyContinue"
1112

12-
function Info($msg) { Write-Host "vix install: $msg" }
13-
function Die($msg) { throw "vix install: $msg" }
13+
function Info([string]$msg) { Write-Host "vix install: $msg" }
14+
function Die([string]$msg) { throw "vix install: $msg" }
1415

1516
$Repo = if ($env:VIX_REPO) { $env:VIX_REPO } else { "vixcpp/vix" }
1617
$Version = if ($env:VIX_VERSION) { $env:VIX_VERSION } else { "latest" }
1718
$InstallDir = if ($env:VIX_INSTALL_DIR) { $env:VIX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Vix\bin" }
1819
$BinName = "vix.exe"
1920

21+
$StatsFile = if ($env:VIX_STATS_FILE) { $env:VIX_STATS_FILE } else { Join-Path $env:LOCALAPPDATA "Vix\install.json" }
22+
$StatsDir = Split-Path -Parent $StatsFile
23+
24+
function Normalize-Dir([string]$p) {
25+
if (-not $p) { return $p }
26+
try {
27+
$full = [System.IO.Path]::GetFullPath($p)
28+
return $full.TrimEnd('\')
29+
} catch {
30+
return $p.TrimEnd('\')
31+
}
32+
}
33+
2034
function Resolve-LatestTag([string]$repo) {
21-
# Robust way: call GitHub API (no auth needed for low volume)
2235
$api = "https://api.github.com/repos/$repo/releases/latest"
2336
try {
2437
$resp = Invoke-RestMethod -Uri $api -Headers @{ "User-Agent" = "vix-installer" }
2538
if (-not $resp.tag_name) { Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z" }
26-
return $resp.tag_name
39+
return [string]$resp.tag_name
2740
} catch {
2841
Die "could not resolve latest tag (GitHub API). Set VIX_VERSION=vX.Y.Z"
2942
}
3043
}
3144

32-
$Tag = if ($Version -eq "latest") { Resolve-LatestTag $Repo } else { $Version }
45+
function Get-RemoteContentLength([string]$url) {
46+
try {
47+
$req = [System.Net.HttpWebRequest]::Create($url)
48+
$req.Method = "HEAD"
49+
$req.UserAgent = "vix-installer"
50+
$req.AllowAutoRedirect = $true
51+
$resp = $req.GetResponse()
52+
try {
53+
$len = $resp.ContentLength
54+
if ($len -gt 0) { return [int64]$len }
55+
return $null
56+
} finally {
57+
$resp.Close()
58+
}
59+
} catch {
60+
return $null
61+
}
62+
}
63+
64+
function Format-Bytes([Int64]$bytes) {
65+
if ($bytes -lt 1024) { return "$bytes B" }
66+
$units = @("KB","MB","GB","TB")
67+
$v = [double]$bytes
68+
$i = 0
69+
while ($v -ge 1024 -and $i -lt $units.Length) {
70+
$v /= 1024
71+
$i++
72+
}
73+
if ($i -eq 0) { return ("{0:N2} KB" -f ($bytes / 1024.0)) }
74+
return ("{0:N2} {1}" -f $v, $units[$i-1])
75+
}
76+
77+
function Extract-VersionToken([string]$verText) {
78+
if (-not $verText) { return $null }
79+
$m = [regex]::Match($verText, 'v\d+\.\d+\.\d+([-.+][0-9A-Za-z\.-]+)?')
80+
if ($m.Success) { return $m.Value }
81+
return $null
82+
}
83+
84+
function Get-InstalledVersion([string]$exePath) {
85+
if (-not (Test-Path -LiteralPath $exePath)) { return $null }
86+
try {
87+
$raw = & $exePath --version 2>$null
88+
if (-not $raw) { return $null }
89+
return (Extract-VersionToken ([string]$raw))
90+
} catch {
91+
return $null
92+
}
93+
}
94+
95+
function Write-InstallStats(
96+
[string]$repo,
97+
[string]$tag,
98+
[string]$arch,
99+
[string]$installDir,
100+
[Nullable[Int64]]$downloadBytes,
101+
[string]$installedVersion,
102+
[string]$assetUrl
103+
) {
104+
New-Item -ItemType Directory -Force -Path $StatsDir | Out-Null
105+
106+
$obj = [ordered]@{
107+
repo = $repo
108+
version = $tag
109+
installed_version = $installedVersion
110+
installed_at = [DateTime]::UtcNow.ToString("o")
111+
os = "windows"
112+
arch = $arch
113+
install_dir = $installDir
114+
download_bytes = $downloadBytes
115+
asset_url = $assetUrl
116+
}
117+
118+
($obj | ConvertTo-Json -Depth 6) | Set-Content -LiteralPath $StatsFile -Encoding UTF8
119+
Info "wrote stats: $StatsFile"
120+
}
33121

34-
# Detect arch (prefer OS bitness + ARM check)
35-
$archRaw = $env:PROCESSOR_ARCHITECTURE
36-
$Arch = switch -Regex ($archRaw) {
37-
"AMD64" { "x86_64"; break }
38-
"^ARM" { "aarch64"; break }
39-
default { Die "unsupported arch: $archRaw" }
122+
# Detect arch (stable across environments)
123+
$Arch = if ([Environment]::Is64BitOperatingSystem) {
124+
if ($env:PROCESSOR_ARCHITECTURE -match '^ARM' -or $env:PROCESSOR_ARCHITEW6432 -match '^ARM') { "aarch64" } else { "x86_64" }
125+
} else {
126+
Die "unsupported OS: 32-bit Windows"
40127
}
41128

129+
$Tag = if ($Version -eq "latest") { Resolve-LatestTag $Repo } else { $Version }
130+
42131
$Asset = "vix-windows-$Arch.zip"
43132
$BaseUrl = "https://github.com/$Repo/releases/download/$Tag"
44133
$UrlBin = "$BaseUrl/$Asset"
45134
$UrlSha = "$UrlBin.sha256"
46135

136+
$InstallDirNorm = Normalize-Dir $InstallDir
137+
$Exe = Join-Path $InstallDirNorm $BinName
138+
47139
Info "repo=$Repo version=$Tag arch=$Arch"
48-
Info "install_dir=$InstallDir"
140+
Info "install_dir=$InstallDirNorm"
141+
142+
# Skip if already installed and matches (but still write stats)
143+
$installedTag = Get-InstalledVersion $Exe
144+
if ($installedTag -and $installedTag -eq $Tag) {
145+
Info "already installed: $installedTag (no download needed)"
146+
Write-InstallStats $Repo $Tag $Arch $InstallDirNorm $null $installedTag $UrlBin
147+
Info "done"
148+
exit 0
149+
}
49150

50151
# Temp dir unique
51152
$TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("vix-" + [System.Guid]::NewGuid().ToString("N"))
52153
New-Item -ItemType Directory -Force -Path $TmpDir | Out-Null
154+
53155
try {
54156
$ZipPath = Join-Path $TmpDir $Asset
55157
$ShaPath = Join-Path $TmpDir ($Asset + ".sha256")
56158

159+
# Print size (best effort)
160+
$len = Get-RemoteContentLength $UrlBin
161+
if ($len) { Info ("download size: {0} ({1} bytes)" -f (Format-Bytes $len), $len) }
162+
else { Info "download size: unknown (no Content-Length)" }
163+
57164
Info "downloading: $UrlBin"
58-
Invoke-WebRequest -Uri $UrlBin -OutFile $ZipPath
165+
Invoke-WebRequest -Uri $UrlBin -OutFile $ZipPath -Headers @{ "User-Agent" = "vix-installer" }
59166

60-
# SHA256 verification policy:
61-
# - If sha256 file exists -> MUST verify and match.
62-
# - If sha256 missing -> warn (optionally you can hard-fail; currently warn).
167+
# Verification policy: sha256 required
63168
Info "trying sha256 verification..."
64-
$shaOk = $false
169+
$haveSha = $false
65170
try {
66-
Invoke-WebRequest -Uri $UrlSha -OutFile $ShaPath
171+
Invoke-WebRequest -Uri $UrlSha -OutFile $ShaPath -Headers @{ "User-Agent" = "vix-installer" }
172+
$haveSha = $true
67173

68174
$first = (Get-Content -LiteralPath $ShaPath -TotalCount 1).Trim()
69175
if (-not $first) { Die "invalid sha256 file" }
70176

71-
# Accept:
72-
# 1) "<sha> file"
73-
# 2) "SHA256 (file) = <sha>"
74177
$expected = $null
75-
if ($first -match "^[0-9a-fA-F]{64}") {
76-
$expected = ($first -split "\s+")[0]
77-
} elseif ($first -match "=\s*([0-9a-fA-F]{64})\s*$") {
78-
$expected = $Matches[1]
79-
}
178+
if ($first -match "^[0-9a-fA-F]{64}") { $expected = ($first -split "\s+")[0] }
179+
elseif ($first -match "=\s*([0-9a-fA-F]{64})\s*$") { $expected = $Matches[1] }
80180
if (-not $expected) { Die "invalid sha256 format" }
81181

82182
$actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ZipPath).Hash
83-
if ($expected.ToLower() -ne $actual.ToLower()) { Die "sha256 mismatch" }
183+
if ($expected.ToLowerInvariant() -ne $actual.ToLowerInvariant()) { Die "sha256 mismatch" }
84184

85-
$shaOk = $true
86185
Info "sha256 ok"
87186
} catch {
88-
Info "sha256 file not found (skipping)"
187+
if ($haveSha) { throw }
188+
Die "sha256 file not found ($UrlSha). refusing to install."
89189
}
90190

91-
# Extract to temp first, then move only vix.exe (avoids zip path layout issues)
191+
# Extract
92192
$ExtractDir = Join-Path $TmpDir "extract"
93193
New-Item -ItemType Directory -Force -Path $ExtractDir | Out-Null
94194
Expand-Archive -LiteralPath $ZipPath -DestinationPath $ExtractDir -Force
95195

96-
# Find vix.exe anywhere in archive
97196
$ExeCandidate = Get-ChildItem -LiteralPath $ExtractDir -Recurse -File -Filter $BinName | Select-Object -First 1
98197
if (-not $ExeCandidate) { Die "archive does not contain $BinName" }
99198

100-
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
101-
$Exe = Join-Path $InstallDir $BinName
102-
Copy-Item -LiteralPath $ExeCandidate.FullName -Destination $Exe -Force
199+
New-Item -ItemType Directory -Force -Path $InstallDirNorm | Out-Null
200+
201+
# Atomic-ish install: temp then move
202+
$TmpExe = Join-Path $InstallDirNorm ("$BinName.tmp." + [System.Diagnostics.Process]::GetCurrentProcess().Id)
203+
Copy-Item -LiteralPath $ExeCandidate.FullName -Destination $TmpExe -Force
204+
Move-Item -LiteralPath $TmpExe -Destination $Exe -Force
103205

104206
Info "installed to $Exe"
105207

106-
# Add to user PATH (idempotent + exact segment check)
208+
# Add to user PATH (idempotent)
107209
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
108210
if (-not $userPath) { $userPath = "" }
109211

110212
$segments = $userPath -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
111213
$already = $false
112214
foreach ($s in $segments) {
113-
if ([string]::Equals($s.TrimEnd("\"), $InstallDir.TrimEnd("\"), [System.StringComparison]::OrdinalIgnoreCase)) {
114-
$already = $true
115-
break
116-
}
215+
if ([string]::Equals((Normalize-Dir $s), $InstallDirNorm, [System.StringComparison]::OrdinalIgnoreCase)) { $already = $true; break }
117216
}
118217

119218
if (-not $already) {
120-
$newPath = ($segments + $InstallDir) -join ";"
219+
$newPath = ($segments + $InstallDirNorm) -join ";"
121220
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
122221
Info "added to PATH (restart your terminal)"
123222
} else {
124223
Info "PATH already contains install_dir"
125224
}
126225

127-
# Quick check
226+
# Quick check + stats
227+
$installedVersion = $null
128228
try {
129-
$ver = & $Exe --version 2>$null
130-
if ($ver) { Info "version: $ver" }
229+
$raw = & $Exe --version 2>$null
230+
if ($raw) { Info "version: $raw" }
231+
$installedVersion = Extract-VersionToken ([string]$raw)
131232
} catch { }
132233

234+
if (-not $installedVersion) { $installedVersion = $Tag }
235+
236+
$downloadBytes = $null
237+
if ($len) { $downloadBytes = [int64]$len }
238+
239+
Write-InstallStats $Repo $Tag $Arch $InstallDirNorm $downloadBytes $installedVersion $UrlBin
240+
133241
Info "done"
134242
}
135243
finally {

0 commit comments

Comments
 (0)