|
1 | | -#!/usr/bin/env sh |
2 | | -set -eu |
3 | | - |
4 | | -# minisign public key (ONLY the base64 key, without the comment line) |
5 | | -MINISIGN_PUBKEY="RWTB+/RzT24X6uPqrPGKrqODmbchU4N1G00fWzQSUc+qkz7pBUnEys58" |
6 | | - |
7 | | -REPO="${VIX_REPO:-vixcpp/vix}" |
8 | | -VERSION="${VIX_VERSION:-latest}" # "latest" or "v1.20.1" |
9 | | -INSTALL_DIR="${VIX_INSTALL_DIR:-$HOME/.local/bin}" |
10 | | -BIN_NAME="vix" |
11 | | - |
12 | | -die() { printf "vix install: %s\n" "$*" >&2; exit 1; } |
13 | | -info() { printf "vix install: %s\n" "$*" >&2; } |
14 | | - |
15 | | -have() { command -v "$1" >/dev/null 2>&1; } |
16 | | -need_cmd() { have "$1" || die "missing dependency: $1"; } |
17 | | - |
18 | | -fetch() { |
19 | | - url="$1" |
20 | | - out="$2" |
21 | | - if have curl; then |
22 | | - curl -fsSL "$url" -o "$out" >/dev/null 2>&1 |
23 | | - elif have wget; then |
24 | | - wget -qO "$out" "$url" >/dev/null 2>&1 |
25 | | - else |
26 | | - die "need curl or wget" |
27 | | - fi |
| 1 | +# Vix.cpp installer (Windows PowerShell) |
| 2 | +# Usage: |
| 3 | +# irm https://vixcpp.com/install.ps1 | iex |
| 4 | +# Optional: |
| 5 | +# $env:VIX_VERSION="v1.20.1" |
| 6 | +# $env:VIX_INSTALL_DIR="$env:LOCALAPPDATA\Vix\bin" |
| 7 | +# $env:VIX_REPO="vixcpp/vix" |
| 8 | + |
| 9 | +$ErrorActionPreference = "Stop" |
| 10 | +$ProgressPreference = "SilentlyContinue" |
| 11 | + |
| 12 | +function Info($msg) { Write-Host "vix install: $msg" } |
| 13 | +function Die($msg) { throw "vix install: $msg" } |
| 14 | + |
| 15 | +$Repo = if ($env:VIX_REPO) { $env:VIX_REPO } else { "vixcpp/vix" } |
| 16 | +$Version = if ($env:VIX_VERSION) { $env:VIX_VERSION } else { "latest" } |
| 17 | +$InstallDir = if ($env:VIX_INSTALL_DIR) { $env:VIX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Vix\bin" } |
| 18 | +$BinName = "vix.exe" |
| 19 | + |
| 20 | +# minisign public key (base64 only) |
| 21 | +$MiniSignPubKey = "RWSIfpPSznK9A1gWUc8Eg2iXXQwU5d9BYuQNKGOcoujAF2stPu5rKFjQ" |
| 22 | + |
| 23 | +function Resolve-LatestTag([string]$repo) { |
| 24 | + $api = "https://api.github.com/repos/$repo/releases/latest" |
| 25 | + try { |
| 26 | + $resp = Invoke-RestMethod -Uri $api -Headers @{ "User-Agent" = "vix-installer" } |
| 27 | + if (-not $resp.tag_name) { Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z" } |
| 28 | + return $resp.tag_name |
| 29 | + } catch { |
| 30 | + Die "could not resolve latest tag (GitHub API). Set VIX_VERSION=vX.Y.Z" |
| 31 | + } |
28 | 32 | } |
29 | 33 |
|
30 | | -need_cmd uname |
31 | | -need_cmd mktemp |
32 | | -need_cmd tar |
33 | | - |
34 | | -os="$(uname -s | tr '[:upper:]' '[:lower:]')" |
35 | | -arch="$(uname -m)" |
36 | | - |
37 | | -case "$os" in |
38 | | - linux) OS="linux" ;; |
39 | | - darwin) OS="macos" ;; |
40 | | - *) die "unsupported OS: $os (only Linux/macOS supported by install.sh)" ;; |
41 | | -esac |
42 | | - |
43 | | -case "$arch" in |
44 | | - x86_64|amd64) ARCH="x86_64" ;; |
45 | | - arm64|aarch64) ARCH="aarch64" ;; |
46 | | - *) die "unsupported CPU arch: $arch" ;; |
47 | | -esac |
48 | | - |
49 | | -TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t vix)" |
50 | | -cleanup() { rm -rf "$TMP_DIR"; } |
51 | | -trap cleanup EXIT INT TERM |
52 | | - |
53 | | -resolve_version() { |
54 | | - if [ "$VERSION" = "latest" ]; then |
55 | | - have curl || die "curl is required to resolve latest (or set VIX_VERSION=vX.Y.Z)" |
56 | | - final="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest")" |
57 | | - tag="${final##*/}" |
58 | | - [ -n "$tag" ] || die "could not resolve latest version" |
59 | | - printf "%s" "$tag" |
60 | | - else |
61 | | - printf "%s" "$VERSION" |
62 | | - fi |
| 34 | +$Tag = if ($Version -eq "latest") { Resolve-LatestTag $Repo } else { $Version } |
| 35 | + |
| 36 | +# Detect arch |
| 37 | +$archRaw = $env:PROCESSOR_ARCHITECTURE |
| 38 | +$Arch = switch -Regex ($archRaw) { |
| 39 | + "AMD64" { "x86_64"; break } |
| 40 | + "^ARM" { "aarch64"; break } |
| 41 | + default { Die "unsupported arch: $archRaw" } |
63 | 42 | } |
64 | 43 |
|
65 | | -TAG="$(resolve_version)" |
66 | | -info "repo=$REPO version=$TAG os=$OS arch=$ARCH" |
67 | | - |
68 | | -ASSET="vix-${OS}-${ARCH}.tar.gz" |
69 | | -BASE_URL="https://github.com/${REPO}/releases/download/${TAG}" |
70 | | -URL_BIN="${BASE_URL}/${ASSET}" |
71 | | -URL_SHA="${URL_BIN}.sha256" |
72 | | -URL_MINISIG="${URL_BIN}.minisig" |
73 | | - |
74 | | -bin_tgz="${TMP_DIR}/${ASSET}" |
75 | | -sha_file="${TMP_DIR}/${ASSET}.sha256" |
76 | | -sig_file="${TMP_DIR}/${ASSET}.minisig" |
77 | | - |
78 | | -info "downloading: $URL_BIN" |
79 | | -fetch "$URL_BIN" "$bin_tgz" || die "download failed" |
80 | | - |
81 | | -# ---- Verification policy ---- |
82 | | -# Require at least one verification method (sha256 or minisign). |
83 | | -have_sha=0 |
84 | | -have_sig=0 |
85 | | - |
86 | | -info "trying sha256 verification..." |
87 | | -if fetch "$URL_SHA" "$sha_file"; then |
88 | | - have_sha=1 |
89 | | - if ! have sha256sum && ! have shasum; then |
90 | | - die "need sha256sum (Linux) or shasum (macOS) for verification" |
91 | | - fi |
92 | | - |
93 | | - # support both formats: |
94 | | - # 1) "<sha> <file>" |
95 | | - # 2) "SHA256 (file) = <sha>" |
96 | | - expected="$( |
97 | | - awk ' |
98 | | - /^[0-9a-fA-F]{64}/ { print $1; exit } |
99 | | - /^SHA256 \(/ { print $NF; exit } |
100 | | - ' "$sha_file" |
101 | | - )" |
102 | | - [ -n "$expected" ] || die "invalid sha256 file" |
103 | | - |
104 | | - if have sha256sum; then |
105 | | - actual="$(sha256sum "$bin_tgz" | awk '{print $1}')" |
106 | | - else |
107 | | - actual="$(shasum -a 256 "$bin_tgz" | awk '{print $1}')" |
108 | | - fi |
109 | | - |
110 | | - [ "$expected" = "$actual" ] || die "sha256 mismatch" |
111 | | - info "sha256 ok" |
112 | | -else |
113 | | - info "sha256 file not found" |
114 | | -fi |
115 | | - |
116 | | -info "trying minisign verification..." |
117 | | -if fetch "$URL_MINISIG" "$sig_file"; then |
118 | | - have_sig=1 |
119 | | - have minisign || die "minisig is published but minisign is not installed" |
120 | | - minisign -Vm "$bin_tgz" -P "$MINISIGN_PUBKEY" >/dev/null 2>&1 \ |
121 | | - || die "minisign verification failed" |
122 | | - info "minisign ok" |
123 | | -else |
124 | | - info "minisig not found" |
125 | | -fi |
126 | | - |
127 | | -if [ "$have_sha" -eq 0 ] && [ "$have_sig" -eq 0 ]; then |
128 | | - die "no verification file found (.sha256 or .minisig). refusing to install." |
129 | | -fi |
130 | | - |
131 | | -# Extract and install |
132 | | -mkdir -p "$INSTALL_DIR" |
133 | | -tar -xzf "$bin_tgz" -C "$TMP_DIR" |
134 | | -[ -f "${TMP_DIR}/${BIN_NAME}" ] || die "archive does not contain '${BIN_NAME}'" |
135 | | - |
136 | | -chmod +x "${TMP_DIR}/${BIN_NAME}" |
137 | | -dest="${INSTALL_DIR}/${BIN_NAME}" |
138 | | -info "installing to: $dest" |
139 | | -mv -f "${TMP_DIR}/${BIN_NAME}" "$dest" |
140 | | - |
141 | | -if "$dest" --version >/dev/null 2>&1; then |
142 | | - info "installed: $("$dest" --version 2>/dev/null || true)" |
143 | | -else |
144 | | - info "installed, but running 'vix --version' failed (PATH or runtime issue)" |
145 | | -fi |
146 | | - |
147 | | -case ":$PATH:" in |
148 | | - *":$INSTALL_DIR:"*) : ;; |
149 | | - *) |
150 | | - info "NOTE: '$INSTALL_DIR' is not in your PATH." |
151 | | - info "Add this to your shell config:" |
152 | | - info " export PATH=\"$INSTALL_DIR:\$PATH\"" |
153 | | - ;; |
154 | | -esac |
155 | | - |
156 | | -info "done" |
| 44 | +$Asset = "vix-windows-$Arch.zip" |
| 45 | +$BaseUrl = "https://github.com/$Repo/releases/download/$Tag" |
| 46 | +$UrlBin = "$BaseUrl/$Asset" |
| 47 | +$UrlSha = "$UrlBin.sha256" |
| 48 | +$UrlMiniSig = "$UrlBin.minisig" |
| 49 | + |
| 50 | +Info "repo=$Repo version=$Tag arch=$Arch" |
| 51 | +Info "install_dir=$InstallDir" |
| 52 | + |
| 53 | +# Temp dir unique |
| 54 | +$TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("vix-" + [System.Guid]::NewGuid().ToString("N")) |
| 55 | +New-Item -ItemType Directory -Force -Path $TmpDir | Out-Null |
| 56 | + |
| 57 | +try { |
| 58 | + $ZipPath = Join-Path $TmpDir $Asset |
| 59 | + $ShaPath = Join-Path $TmpDir ($Asset + ".sha256") |
| 60 | + $SigPath = Join-Path $TmpDir ($Asset + ".minisig") |
| 61 | + |
| 62 | + Info "downloading: $UrlBin" |
| 63 | + Invoke-WebRequest -Uri $UrlBin -OutFile $ZipPath |
| 64 | + |
| 65 | + # Require at least one verification method (sha256 or minisign) |
| 66 | + $haveSha = $false |
| 67 | + $haveSig = $false |
| 68 | + |
| 69 | + # --- SHA256 verification --- |
| 70 | + Info "trying sha256 verification..." |
| 71 | + try { |
| 72 | + Invoke-WebRequest -Uri $UrlSha -OutFile $ShaPath |
| 73 | + |
| 74 | + $first = (Get-Content -LiteralPath $ShaPath -TotalCount 1).Trim() |
| 75 | + if (-not $first) { Die "invalid sha256 file" } |
| 76 | + |
| 77 | + # Accept: |
| 78 | + # 1) "<sha> file" |
| 79 | + # 2) "SHA256 (file) = <sha>" |
| 80 | + $expected = $null |
| 81 | + if ($first -match "^[0-9a-fA-F]{64}") { |
| 82 | + $expected = ($first -split "\s+")[0] |
| 83 | + } elseif ($first -match "=\s*([0-9a-fA-F]{64})\s*$") { |
| 84 | + $expected = $Matches[1] |
| 85 | + } |
| 86 | + if (-not $expected) { Die "invalid sha256 format" } |
| 87 | + |
| 88 | + $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ZipPath).Hash |
| 89 | + if ($expected.ToLower() -ne $actual.ToLower()) { Die "sha256 mismatch" } |
| 90 | + |
| 91 | + $haveSha = $true |
| 92 | + Info "sha256 ok" |
| 93 | + } catch { |
| 94 | + Info "sha256 file not found (skipping)" |
| 95 | + } |
| 96 | + |
| 97 | + # --- minisign verification (if minisig exists) --- |
| 98 | + Info "trying minisign verification..." |
| 99 | + try { |
| 100 | + Invoke-WebRequest -Uri $UrlMiniSig -OutFile $SigPath |
| 101 | + $haveSig = $true |
| 102 | + |
| 103 | + $mini = Get-Command minisign -ErrorAction SilentlyContinue |
| 104 | + if (-not $mini) { |
| 105 | + Die "minisig is published but minisign is not installed (install minisign or use sha256-only verification)" |
| 106 | + } |
| 107 | + |
| 108 | + & minisign -V -m $ZipPath -x $SigPath -P $MiniSignPubKey | Out-Null |
| 109 | + Info "minisign ok" |
| 110 | + } catch { |
| 111 | + Info "minisig not found (skipping)" |
| 112 | + } |
| 113 | + |
| 114 | + if (-not $haveSha -and -not $haveSig) { |
| 115 | + Die "no verification file found (.sha256 or .minisig). refusing to install." |
| 116 | + } |
| 117 | + |
| 118 | + # Extract to temp first, then move only vix.exe |
| 119 | + $ExtractDir = Join-Path $TmpDir "extract" |
| 120 | + New-Item -ItemType Directory -Force -Path $ExtractDir | Out-Null |
| 121 | + Expand-Archive -LiteralPath $ZipPath -DestinationPath $ExtractDir -Force |
| 122 | + |
| 123 | + $ExeCandidate = Get-ChildItem -LiteralPath $ExtractDir -Recurse -File -Filter $BinName | Select-Object -First 1 |
| 124 | + if (-not $ExeCandidate) { Die "archive does not contain $BinName" } |
| 125 | + |
| 126 | + New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null |
| 127 | + $Exe = Join-Path $InstallDir $BinName |
| 128 | + Copy-Item -LiteralPath $ExeCandidate.FullName -Destination $Exe -Force |
| 129 | + |
| 130 | + Info "installed to $Exe" |
| 131 | + |
| 132 | + # Add to user PATH (idempotent) |
| 133 | + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") |
| 134 | + if (-not $userPath) { $userPath = "" } |
| 135 | + |
| 136 | + $segments = $userPath -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } |
| 137 | + $already = $false |
| 138 | + foreach ($s in $segments) { |
| 139 | + if ([string]::Equals($s.TrimEnd("\"), $InstallDir.TrimEnd("\"), [System.StringComparison]::OrdinalIgnoreCase)) { |
| 140 | + $already = $true |
| 141 | + break |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + if (-not $already) { |
| 146 | + $newPath = ($segments + $InstallDir) -join ";" |
| 147 | + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") |
| 148 | + Info "added to PATH (restart your terminal)" |
| 149 | + } else { |
| 150 | + Info "PATH already contains install_dir" |
| 151 | + } |
| 152 | + |
| 153 | + # Quick check |
| 154 | + try { |
| 155 | + $ver = & $Exe --version 2>$null |
| 156 | + if ($ver) { Info "version: $ver" } |
| 157 | + } catch { } |
| 158 | + |
| 159 | + Info "done" |
| 160 | +} |
| 161 | +finally { |
| 162 | + Remove-Item -LiteralPath $TmpDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null |
| 163 | +} |
0 commit comments