{{ feature.text }}
+diff --git a/vix-site/public/install.ps1 b/vix-site/public/install.ps1
index 53d97d04..7d94eaa5 100644
--- a/vix-site/public/install.ps1
+++ b/vix-site/public/install.ps1
@@ -3,29 +3,46 @@
# irm https://vixcpp.com/install.ps1 | iex
#
# Optional:
-# $env:VIX_VERSION="v2.7.0"
+# $env:VIX_VERSION="v2.7.8"
# $env:VIX_REPO="vixcpp/vix"
+# $env:VIX_STABLE_URL="https://vixcpp.com/releases/stable.txt"
+# $env:VIX_FALLBACK_VERSION="v2.7.8"
# $env:VIX_INSTALL_DIR="$env:LOCALAPPDATA\Vix\bin"
+# $env:VIX_INSTALL_SHARE_DIR="$env:LOCALAPPDATA\Vix\share"
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
+# Ensure GitHub and vixcpp.com work on Windows PowerShell 5.1.
+try {
+ [Net.ServicePointManager]::SecurityProtocol =
+ [Net.ServicePointManager]::SecurityProtocol -bor
+ [Net.SecurityProtocolType]::Tls12
+} catch {
+ # PowerShell editions using HttpClient do not require this setting.
+}
+
$MinisignPubkey = "RWSIfpPSznK9A1gWUc8Eg2iXXQwU5d9BYuQNKGOcoujAF2stPu5rKFjQ"
+$RequestHeaders = @{ "User-Agent" = "vix-installer" }
+
+function Step([string]$Message) {
+ Write-Host " → $Message"
+}
-function Step($msg) {
- Write-Host " → $msg"
+function Ok([string]$Message) {
+ Write-Host " ✓ $Message" -ForegroundColor Green
}
-function Ok($msg) {
- Write-Host " ✓ $msg" -ForegroundColor Green
+function Warn([string]$Message) {
+ Write-Host " ! $Message" -ForegroundColor Yellow
}
-function Hint($msg) {
- Write-Host " · $msg" -ForegroundColor DarkGray
+function Hint([string]$Message) {
+ Write-Host " · $Message" -ForegroundColor DarkGray
}
-function Die($msg) {
- Write-Host " ✗ $msg" -ForegroundColor Red
+function Die([string]$Message) {
+ Write-Host " ✗ $Message" -ForegroundColor Red
exit 1
}
@@ -37,12 +54,53 @@ Usage:
install.ps1
Environment:
- VIX_VERSION Release version. Example: v2.7.0. Default: latest
- VIX_REPO GitHub repo. Default: vixcpp/vix
- VIX_INSTALL_DIR CLI bin dir. Default: %LOCALAPPDATA%\Vix\bin
+ VIX_VERSION
+ Release version to install.
+
+ Examples:
+ latest
+ v2.7.8
+ v2.8.3
+
+ Default: latest
+
+ "latest" means the latest release validated by the complete
+ Vix release CI, not necessarily the newest GitHub tag.
+
+ VIX_STABLE_URL
+ URL containing the latest validated release tag.
+
+ Default:
+ https://vixcpp.com/releases/stable.txt
+
+ VIX_FALLBACK_VERSION
+ Emergency fallback used when the stable release pointer is
+ unavailable, invalid, or incomplete.
+
+ Default:
+ v2.7.8
+
+ VIX_REPO
+ GitHub repository containing release assets.
+
+ Default:
+ vixcpp/vix
+
+ VIX_INSTALL_DIR
+ CLI installation directory.
+
+ Default:
+ %LOCALAPPDATA%\Vix\bin
+
+ VIX_INSTALL_SHARE_DIR
+ Runtime assets installation directory.
-After install:
+ Default:
+ %LOCALAPPDATA%\Vix\share
+
+After installation:
vix upgrade
+ vix upgrade --check
vix upgrade --sdk list
vix upgrade --sdk web
"@
@@ -54,19 +112,24 @@ foreach ($arg in $args) {
Show-Help
exit 0
}
+
"-h" {
Show-Help
exit 0
}
+
"--cli-only" {
- # Kept for compatibility. The installer is CLI-only now.
+ # Kept for backward compatibility.
}
+
"--cli" {
- # Kept for compatibility. The installer is CLI-only now.
+ # Kept for backward compatibility.
}
+
"--sdk" {
- Die "SDK install moved to: vix upgrade --sdk"
+ Die "SDK installation moved to: vix upgrade --sdk"
}
+
default {
Die "unknown option: $arg"
}
@@ -85,48 +148,172 @@ $Version = if ($env:VIX_VERSION) {
"latest"
}
+$StableUrl = if ($env:VIX_STABLE_URL) {
+ $env:VIX_STABLE_URL
+} else {
+ "https://vixcpp.com/releases/stable.txt"
+}
+
+$FallbackVersion = if ($env:VIX_FALLBACK_VERSION) {
+ $env:VIX_FALLBACK_VERSION
+} else {
+ "v2.7.8"
+}
+
$BinDir = if ($env:VIX_INSTALL_DIR) {
$env:VIX_INSTALL_DIR
} else {
Join-Path $env:LOCALAPPDATA "Vix\bin"
}
-$BinName = "vix.exe"
+$ShareDir = if ($env:VIX_INSTALL_SHARE_DIR) {
+ $env:VIX_INSTALL_SHARE_DIR
+} else {
+ $installRoot = Split-Path -Parent $BinDir
+ Join-Path $installRoot "share"
+}
-function Resolve-LatestTag([string]$repo) {
- $api = "https://api.github.com/repos/$repo/releases/latest"
+$BinName = "vix.exe"
- try {
- $resp = Invoke-RestMethod -Uri $api -Headers @{ "User-Agent" = "vix-installer" }
+function Test-ReleaseTag([string]$Tag) {
+ if ([string]::IsNullOrWhiteSpace($Tag)) {
+ return $false
+ }
- if (-not $resp.tag_name) {
- Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z"
- }
+ return $Tag -match '^v[0-9]+\.[0-9]+\.[0-9]+$'
+}
- return $resp.tag_name
- } catch {
- Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z"
+function Get-NativeArchitectureName {
+ if ($env:PROCESSOR_ARCHITEW6432) {
+ return $env:PROCESSOR_ARCHITEW6432
}
+
+ return $env:PROCESSOR_ARCHITECTURE
}
function Detect-Architecture {
- $archRaw = $env:PROCESSOR_ARCHITECTURE
+ $archRaw = Get-NativeArchitectureName
switch -Regex ($archRaw) {
- "AMD64" {
+ "^AMD64$" {
return "x86_64"
}
- "^ARM" {
+
+ "^ARM64$" {
return "aarch64"
}
+
default {
Die "unsupported architecture: $archRaw"
}
}
}
-function Verify-Checksum([string]$archivePath, [string]$shaPath) {
- $first = (Get-Content -LiteralPath $shaPath -TotalCount 1).Trim()
+function Resolve-StableTag {
+ try {
+ $response = Invoke-WebRequest `
+ -Uri $StableUrl `
+ -Headers $RequestHeaders `
+ -UseBasicParsing
+
+ $lines = ([string]$response.Content) -split '\r?\n'
+
+ $tag = $lines |
+ ForEach-Object { $_.Trim() } |
+ Where-Object { $_ -ne "" } |
+ Select-Object -First 1
+
+ if (-not (Test-ReleaseTag $tag)) {
+ return $null
+ }
+
+ return $tag
+ } catch {
+ return $null
+ }
+}
+
+function Test-UrlExists([string]$Url) {
+ try {
+ Invoke-WebRequest `
+ -Uri $Url `
+ -Method Head `
+ -MaximumRedirection 10 `
+ -Headers $RequestHeaders `
+ -UseBasicParsing |
+ Out-Null
+
+ return $true
+ } catch {
+ return $false
+ }
+}
+
+function Test-ReleaseInstallable(
+ [string]$Tag,
+ [string]$Repository,
+ [string]$AssetName
+) {
+ if (-not (Test-ReleaseTag $Tag)) {
+ return $false
+ }
+
+ $baseUrl = "https://github.com/$Repository/releases/download/$Tag"
+
+ if (-not (Test-UrlExists "$baseUrl/$AssetName")) {
+ return $false
+ }
+
+ if (-not (Test-UrlExists "$baseUrl/$AssetName.sha256")) {
+ return $false
+ }
+
+ return $true
+}
+
+function Resolve-Version(
+ [string]$RequestedVersion,
+ [string]$Repository,
+ [string]$AssetName
+) {
+ if ($RequestedVersion -ne "latest") {
+ if (-not (Test-ReleaseTag $RequestedVersion)) {
+ Die "invalid release version: $RequestedVersion"
+ }
+
+ if (-not (Test-ReleaseInstallable $RequestedVersion $Repository $AssetName)) {
+ Die "release $RequestedVersion is incomplete for windows/$Arch"
+ }
+
+ return $RequestedVersion
+ }
+
+ $stable = Resolve-StableTag
+
+ if ($stable) {
+ if (Test-ReleaseInstallable $stable $Repository $AssetName) {
+ return $stable
+ }
+
+ Warn "validated stable release $stable is incomplete for windows/$Arch"
+ } else {
+ Warn "could not resolve the validated stable release"
+ }
+
+ if (
+ (Test-ReleaseTag $FallbackVersion) -and
+ ($FallbackVersion -ne $stable) -and
+ (Test-ReleaseInstallable $FallbackVersion $Repository $AssetName)
+ ) {
+ Step "Falling back to stable release $FallbackVersion"
+ return $FallbackVersion
+ }
+
+ Die "no installable Vix release found for windows/$Arch"
+}
+
+function Verify-Checksum([string]$ArchivePath, [string]$ShaPath) {
+ $first = (Get-Content -LiteralPath $ShaPath -TotalCount 1).Trim()
if (-not $first) {
Die "invalid sha256 file"
@@ -134,8 +321,8 @@ function Verify-Checksum([string]$archivePath, [string]$shaPath) {
$expected = $null
- if ($first -match "^[0-9a-fA-F]{64}") {
- $expected = ($first -split "\s+")[0]
+ if ($first -match "^([0-9a-fA-F]{64})(?:\s+.*)?$") {
+ $expected = $Matches[1]
} elseif ($first -match "=\s*([0-9a-fA-F]{64})\s*$") {
$expected = $Matches[1]
}
@@ -144,21 +331,26 @@ function Verify-Checksum([string]$archivePath, [string]$shaPath) {
Die "invalid sha256 format"
}
- $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash
+ $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ArchivePath).Hash
if ($expected.ToLowerInvariant() -ne $actual.ToLowerInvariant()) {
Die "sha256 mismatch"
}
}
-function Verify-Signature([string]$archivePath, [string]$sigPath) {
+function Verify-Signature([string]$ArchivePath, [string]$SigPath) {
$minisign = Get-Command minisign -ErrorAction SilentlyContinue
if (-not $minisign) {
+ Hint "minisign is not installed; signature verification skipped"
return
}
- & minisign -Vm $archivePath -x $sigPath -P $MinisignPubkey *> $null
+ & $minisign.Path `
+ -Vm $ArchivePath `
+ -x $SigPath `
+ -P $MinisignPubkey `
+ *> $null
if ($LASTEXITCODE -ne 0) {
Die "signature verification failed"
@@ -167,61 +359,90 @@ function Verify-Signature([string]$archivePath, [string]$sigPath) {
Ok "minisign verified"
}
-function Download-And-Verify-Asset([string]$baseUrl, [string]$asset, [string]$tmpDir) {
- $archivePath = Join-Path $tmpDir $asset
- $shaPath = Join-Path $tmpDir ($asset + ".sha256")
- $sigPath = Join-Path $tmpDir ($asset + ".minisig")
+function Download-And-Verify-Asset(
+ [string]$BaseUrl,
+ [string]$AssetName,
+ [string]$TmpDir
+) {
+ $archivePath = Join-Path $TmpDir $AssetName
+ $shaPath = Join-Path $TmpDir ($AssetName + ".sha256")
+ $sigPath = Join-Path $TmpDir ($AssetName + ".minisig")
- $assetUrl = "$baseUrl/$asset"
- $shaUrl = "$baseUrl/$asset.sha256"
- $sigUrl = "$baseUrl/$asset.minisig"
+ $assetUrl = "$BaseUrl/$AssetName"
+ $shaUrl = "$BaseUrl/$AssetName.sha256"
+ $sigUrl = "$BaseUrl/$AssetName.minisig"
- Step "Downloading $asset"
+ Step "Downloading $AssetName"
try {
- Invoke-WebRequest -Uri $assetUrl -OutFile $archivePath
+ Invoke-WebRequest `
+ -Uri $assetUrl `
+ -OutFile $archivePath `
+ -Headers $RequestHeaders `
+ -UseBasicParsing |
+ Out-Null
} catch {
- Die "release asset not found: $asset"
+ Die "release asset not found: $AssetName"
}
try {
- Invoke-WebRequest -Uri $shaUrl -OutFile $shaPath
+ Invoke-WebRequest `
+ -Uri $shaUrl `
+ -OutFile $shaPath `
+ -Headers $RequestHeaders `
+ -UseBasicParsing |
+ Out-Null
} catch {
- Die "checksum file not found: $asset.sha256"
+ Die "checksum file not found: $AssetName.sha256"
}
Verify-Checksum $archivePath $shaPath
Ok "sha256 verified"
+ $signatureDownloaded = $false
+
try {
- Invoke-WebRequest -Uri $sigUrl -OutFile $sigPath
- Verify-Signature $archivePath $sigPath
+ Invoke-WebRequest `
+ -Uri $sigUrl `
+ -OutFile $sigPath `
+ -Headers $RequestHeaders `
+ -UseBasicParsing |
+ Out-Null
+
+ $signatureDownloaded = $true
} catch {
- # minisign is optional for bootstrap install.
+ $signatureDownloaded = $false
+ }
+
+ if ($signatureDownloaded) {
+ Verify-Signature $archivePath $sigPath
}
return $archivePath
}
-function Install-SqliteDll([string]$installBin, [string]$tmpDir) {
- $sqliteDll = Join-Path $installBin "sqlite3.dll"
+function Install-SqliteDll([string]$InstallBin, [string]$TmpDir) {
+ $sqliteDll = Join-Path $InstallBin "sqlite3.dll"
- if (Test-Path -LiteralPath $sqliteDll) {
+ if (Test-Path -LiteralPath $sqliteDll -PathType Leaf) {
return
}
- $archRaw = $env:PROCESSOR_ARCHITECTURE
+ $archRaw = Get-NativeArchitectureName
switch -Regex ($archRaw) {
- "AMD64" {
+ "^AMD64$" {
$sqliteAsset = "sqlite-dll-win-x64-3530200.zip"
}
- "^ARM" {
+
+ "^ARM64$" {
$sqliteAsset = "sqlite-dll-win-arm64-3530200.zip"
}
- "x86" {
+
+ "^x86$" {
$sqliteAsset = "sqlite-dll-win-x86-3530200.zip"
}
+
default {
Hint "sqlite runtime skipped: unsupported architecture $archRaw"
return
@@ -229,29 +450,41 @@ function Install-SqliteDll([string]$installBin, [string]$tmpDir) {
}
$sqliteUrl = "https://www.sqlite.org/2026/$sqliteAsset"
- $sqliteDir = Join-Path $tmpDir "sqlite"
+ $sqliteDir = Join-Path $TmpDir "sqlite"
$sqliteZip = Join-Path $sqliteDir $sqliteAsset
New-Item -ItemType Directory -Force -Path $sqliteDir | Out-Null
- New-Item -ItemType Directory -Force -Path $installBin | Out-Null
+ New-Item -ItemType Directory -Force -Path $InstallBin | Out-Null
Step "Installing SQLite runtime"
try {
- Invoke-WebRequest -Uri $sqliteUrl -OutFile $sqliteZip
+ Invoke-WebRequest `
+ -Uri $sqliteUrl `
+ -OutFile $sqliteZip `
+ -Headers $RequestHeaders `
+ -UseBasicParsing |
+ Out-Null
} catch {
Hint "sqlite runtime skipped"
return
}
try {
- Expand-Archive -LiteralPath $sqliteZip -DestinationPath $sqliteDir -Force
+ Expand-Archive `
+ -LiteralPath $sqliteZip `
+ -DestinationPath $sqliteDir `
+ -Force
} catch {
Hint "sqlite runtime skipped"
return
}
- $dllCandidate = Get-ChildItem -LiteralPath $sqliteDir -Recurse -File -Filter "sqlite3.dll" |
+ $dllCandidate = Get-ChildItem `
+ -LiteralPath $sqliteDir `
+ -Recurse `
+ -File `
+ -Filter "sqlite3.dll" |
Select-Object -First 1
if (-not $dllCandidate) {
@@ -259,88 +492,155 @@ function Install-SqliteDll([string]$installBin, [string]$tmpDir) {
return
}
- Copy-Item -LiteralPath $dllCandidate.FullName -Destination $sqliteDll -Force
+ Copy-Item `
+ -LiteralPath $dllCandidate.FullName `
+ -Destination $sqliteDll `
+ -Force
- if (Test-Path -LiteralPath $sqliteDll) {
+ if (Test-Path -LiteralPath $sqliteDll -PathType Leaf) {
Ok "sqlite3.dll installed"
}
}
-function Add-To-UserPath([string]$pathToAdd) {
+function Add-To-UserPath([string]$PathToAdd) {
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if (-not $userPath) {
$userPath = ""
}
- $segments = $userPath -split ";" |
+ $segments = @(
+ $userPath -split ";" |
ForEach-Object { $_.Trim() } |
Where-Object { $_ -ne "" }
-
- $already = $false
+ )
foreach ($segment in $segments) {
if ([string]::Equals(
$segment.TrimEnd("\"),
- $pathToAdd.TrimEnd("\"),
+ $PathToAdd.TrimEnd("\"),
[System.StringComparison]::OrdinalIgnoreCase
)) {
- $already = $true
- break
+ return $true
}
}
- if (-not $already) {
- $newPath = ($segments + $pathToAdd) -join ";"
- [Environment]::SetEnvironmentVariable("Path", $newPath, "User")
- return $false
- }
+ $newPath = (@($segments) + $PathToAdd) -join ";"
+ [Environment]::SetEnvironmentVariable("Path", $newPath, "User")
- return $true
+ return $false
}
-function Install-Cli([string]$archivePath, [string]$tmpDir) {
- $extractDir = Join-Path $tmpDir "cli"
+function Install-Cli(
+ [string]$ArchivePath,
+ [string]$TmpDir
+) {
+ $extractDir = Join-Path $TmpDir "cli"
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
+ New-Item -ItemType Directory -Force -Path $ShareDir | Out-Null
- Step "Installing to $BinDir\$BinName"
+ Step "Extracting $Asset"
- Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force
+ try {
+ Expand-Archive `
+ -LiteralPath $ArchivePath `
+ -DestinationPath $extractDir `
+ -Force
+ } catch {
+ Die "failed to extract $Asset"
+ }
- $exeCandidate = Get-ChildItem -LiteralPath $extractDir -Recurse -File -Filter $BinName |
+ $exeCandidate = Get-ChildItem `
+ -LiteralPath $extractDir `
+ -Recurse `
+ -File `
+ -Filter $BinName |
Select-Object -First 1
if (-not $exeCandidate) {
Die "CLI archive does not contain $BinName"
}
+ $noteSource = Join-Path $extractDir "share\vix\note"
+ $noteDestination = Join-Path $ShareDir "vix\note"
+ $noteParent = Split-Path -Parent $noteDestination
+
+ $noteIndex = Join-Path $noteSource "index.html"
+ $noteCss = Join-Path $noteSource "assets\note.css"
+ $noteJs = Join-Path $noteSource "assets\note.js"
+
+ if (-not (Test-Path -LiteralPath $noteIndex -PathType Leaf)) {
+ Die "CLI archive does not contain Vix Note index.html"
+ }
+
+ if (-not (Test-Path -LiteralPath $noteCss -PathType Leaf)) {
+ Die "CLI archive does not contain Vix Note note.css"
+ }
+
+ if (-not (Test-Path -LiteralPath $noteJs -PathType Leaf)) {
+ Die "CLI archive does not contain Vix Note note.js"
+ }
+
$exe = Join-Path $BinDir $BinName
- if (-not [string]::Equals(
- $exeCandidate.FullName,
- $exe,
- [System.StringComparison]::OrdinalIgnoreCase
- )) {
- Copy-Item -LiteralPath $exeCandidate.FullName -Destination $exe -Force
+ Step "Installing to $exe"
+
+ Copy-Item `
+ -LiteralPath $exeCandidate.FullName `
+ -Destination $exe `
+ -Force
+
+ Step "Installing Vix Note assets to $noteDestination"
+
+ New-Item `
+ -ItemType Directory `
+ -Force `
+ -Path $noteParent |
+ Out-Null
+
+ if (Test-Path -LiteralPath $noteDestination) {
+ Remove-Item `
+ -LiteralPath $noteDestination `
+ -Recurse `
+ -Force
}
- return $exe
-}
+ Copy-Item `
+ -LiteralPath $noteSource `
+ -Destination $noteDestination `
+ -Recurse `
+ -Force
-$Arch = Detect-Architecture
+ $installedIndex = Join-Path $noteDestination "index.html"
+ $installedCss = Join-Path $noteDestination "assets\note.css"
+ $installedJs = Join-Path $noteDestination "assets\note.js"
-$Tag = if ($Version -eq "latest") {
- Resolve-LatestTag $Repo
-} else {
- $Version
+ if (-not (Test-Path -LiteralPath $installedIndex -PathType Leaf)) {
+ Die "failed to install Vix Note index.html"
+ }
+
+ if (-not (Test-Path -LiteralPath $installedCss -PathType Leaf)) {
+ Die "failed to install Vix Note note.css"
+ }
+
+ if (-not (Test-Path -LiteralPath $installedJs -PathType Leaf)) {
+ Die "failed to install Vix Note note.js"
+ }
+
+ Ok "Vix Note assets installed"
+
+ return $exe
}
+$Arch = Detect-Architecture
$Asset = "vix-windows-$Arch.zip"
-$BaseUrl = "https://github.com/$Repo/releases/download/$Tag"
-$TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("vix-" + [System.Guid]::NewGuid().ToString("N"))
+$TmpDir = Join-Path `
+ ([System.IO.Path]::GetTempPath()) `
+ ("vix-" + [System.Guid]::NewGuid().ToString("N"))
+
New-Item -ItemType Directory -Force -Path $TmpDir | Out-Null
try {
@@ -348,6 +648,10 @@ try {
Write-Host "Vix.cpp" -NoNewline -ForegroundColor Green
Write-Host " installer"
Write-Host " ------------------------------------"
+
+ $Tag = Resolve-Version $Version $Repo $Asset
+ $BaseUrl = "https://github.com/$Repo/releases/download/$Tag"
+
Write-Host " version $Tag"
Write-Host " platform windows/$Arch"
Write-Host ""
@@ -361,6 +665,11 @@ try {
try {
& $Exe --version *> $null
+
+ if ($LASTEXITCODE -ne 0) {
+ throw "vix --version returned exit code $LASTEXITCODE"
+ }
+
Ok "Done — vix $Tag installed"
} catch {
Die "installed, but 'vix --version' failed"
@@ -375,5 +684,10 @@ try {
}
}
finally {
- Remove-Item -LiteralPath $TmpDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
+ Remove-Item `
+ -LiteralPath $TmpDir `
+ -Recurse `
+ -Force `
+ -ErrorAction SilentlyContinue |
+ Out-Null
}
diff --git a/vix-site/public/install.sh b/vix-site/public/install.sh
index 9eb8e64d..571863de 100644
--- a/vix-site/public/install.sh
+++ b/vix-site/public/install.sh
@@ -1,20 +1,42 @@
#!/usr/bin/env sh
set -eu
+# Public key used to verify release archives when minisign is installed.
MINISIGN_PUBKEY="RWSIfpPSznK9A1gWUc8Eg2iXXQwU5d9BYuQNKGOcoujAF2stPu5rKFjQ"
VIX_REPO="${VIX_REPO:-vixcpp/vix}"
+
+# "latest" means the latest release validated by the complete release CI.
VIX_VERSION="${VIX_VERSION:-latest}"
+
+# This file must be updated only after:
+# - all CI jobs succeed;
+# - all release assets are uploaded;
+# - checksums are uploaded;
+# - installation tests succeed.
+VIX_STABLE_URL="${VIX_STABLE_URL:-https://vixcpp.com/releases/stable.txt}"
+
+# Emergency fallback used if stable.txt is unavailable, invalid,
+# or points to an incomplete release.
+VIX_FALLBACK_VERSION="${VIX_FALLBACK_VERSION:-v2.7.8}"
+
VIX_INSTALL_BIN_DIR="${VIX_INSTALL_BIN_DIR:-$HOME/.local/bin}"
+VIX_INSTALL_SHARE_DIR="${VIX_INSTALL_SHARE_DIR:-$HOME/.local/share}"
BIN_NAME="vix"
+TMP_DIR=""
+BIN_STAGE=""
+NOTE_STAGE=""
+DEST=""
+
if [ -t 2 ] && [ "${NO_COLOR:-}" = "" ]; then
C_RESET="$(printf '\033[0m')"
C_BOLD="$(printf '\033[1m')"
C_RED="$(printf '\033[31m')"
C_GREEN="$(printf '\033[32m')"
C_YELLOW="$(printf '\033[33m')"
+ C_CYAN="$(printf '\033[36m')"
C_DIM="$(printf '\033[2m')"
else
C_RESET=""
@@ -22,6 +44,7 @@ else
C_RED=""
C_GREEN=""
C_YELLOW=""
+ C_CYAN=""
C_DIM=""
fi
@@ -38,6 +61,10 @@ ok() {
printf " %s✓%s %s\n" "$C_GREEN" "$C_RESET" "$*" >&2
}
+warn() {
+ printf " %s!%s %s\n" "$C_YELLOW" "$C_RESET" "$*" >&2
+}
+
hint() {
printf " · %s%s%s\n" "$C_DIM" "$*" "$C_RESET" >&2
}
@@ -50,21 +77,118 @@ need_cmd() {
have "$1" || die "missing dependency: $1"
}
+cleanup() {
+ if [ -n "${BIN_STAGE:-}" ]; then
+ rm -f "$BIN_STAGE" >/dev/null 2>&1 || true
+ fi
+
+ if [ -n "${NOTE_STAGE:-}" ]; then
+ rm -rf "$NOTE_STAGE" >/dev/null 2>&1 || true
+ fi
+
+ if [ -n "${TMP_DIR:-}" ]; then
+ rm -rf "$TMP_DIR" >/dev/null 2>&1 || true
+ fi
+}
+
fetch() {
url="$1"
out="$2"
if have curl; then
- curl -fsSL "$url" -o "$out" >/dev/null 2>&1
+ curl \
+ -fsSL \
+ --retry 3 \
+ --retry-delay 1 \
+ --connect-timeout 15 \
+ --max-time 300 \
+ "$url" \
+ -o "$out"
return
fi
if have wget; then
- wget -qO "$out" "$url" >/dev/null 2>&1
+ wget \
+ -q \
+ --tries=3 \
+ --timeout=30 \
+ -O "$out" \
+ "$url"
return
fi
- die "need curl or wget"
+ return 1
+}
+
+fetch_text() {
+ url="$1"
+
+ if have curl; then
+ curl \
+ -fsSL \
+ --retry 3 \
+ --retry-delay 1 \
+ --connect-timeout 15 \
+ --max-time 60 \
+ "$url"
+ return
+ fi
+
+ if have wget; then
+ wget \
+ -q \
+ --tries=3 \
+ --timeout=30 \
+ -O- \
+ "$url"
+ return
+ fi
+
+ return 1
+}
+
+url_exists() {
+ url="$1"
+
+ if have curl; then
+ curl \
+ -fsSIL \
+ --retry 2 \
+ --retry-delay 1 \
+ --connect-timeout 15 \
+ --max-time 60 \
+ "$url" \
+ >/dev/null 2>&1
+ return
+ fi
+
+ if have wget; then
+ wget \
+ -q \
+ --spider \
+ --tries=2 \
+ --timeout=30 \
+ "$url" \
+ >/dev/null 2>&1
+ return
+ fi
+
+ return 1
+}
+
+valid_release_tag() {
+ value="$1"
+
+ printf "%s\n" "$value" |
+ awk '
+ /^v[0-9]+\.[0-9]+\.[0-9]+$/ {
+ valid = 1
+ }
+
+ END {
+ exit valid ? 0 : 1
+ }
+ '
}
show_help() {
@@ -75,12 +199,53 @@ Usage:
install.sh
Environment:
- VIX_VERSION Release version. Example: v2.7.0. Default: latest
- VIX_REPO GitHub repo. Default: vixcpp/vix
- VIX_INSTALL_BIN_DIR CLI install dir. Default: \$HOME/.local/bin
+ VIX_VERSION
+ Release version to install.
+
+ Examples:
+ latest
+ v2.7.8
+ v2.8.3
+
+ Default: latest
+
+ "latest" means the latest release validated by the complete
+ Vix release CI, not necessarily the newest GitHub tag.
+
+ VIX_STABLE_URL
+ URL containing the latest validated release tag.
-After install:
+ Default:
+ https://vixcpp.com/releases/stable.txt
+
+ VIX_FALLBACK_VERSION
+ Emergency fallback used when the stable release pointer is
+ unavailable or incomplete.
+
+ Default:
+ v2.7.8
+
+ VIX_REPO
+ GitHub repository containing release assets.
+
+ Default:
+ vixcpp/vix
+
+ VIX_INSTALL_BIN_DIR
+ CLI installation directory.
+
+ Default:
+ \$HOME/.local/bin
+
+ VIX_INSTALL_SHARE_DIR
+ Runtime assets installation directory.
+
+ Default:
+ \$HOME/.local/share
+
+After installation:
vix upgrade
+ vix upgrade --check
vix upgrade --sdk list
vix upgrade --sdk web
EOF
@@ -92,12 +257,16 @@ for arg in "$@"; do
show_help
exit 0
;;
+
--cli-only|--cli)
- # Kept for compatibility. The installer is CLI-only now.
+ # Kept for backward compatibility.
+ # The installer installs the Vix CLI and its required runtime assets.
;;
+
--sdk)
- die "SDK install moved to: vix upgrade --sdk"
+ die "SDK installation moved to: vix upgrade --sdk"
;;
+
*)
die "unknown option: $arg"
;;
@@ -107,6 +276,17 @@ done
need_cmd uname
need_cmd mktemp
need_cmd tar
+need_cmd awk
+need_cmd find
+need_cmd mkdir
+need_cmd rm
+need_cmd cp
+need_cmd mv
+need_cmd chmod
+
+if ! have curl && ! have wget; then
+ die "need curl or wget"
+fi
detect_platform() {
os="$(uname -s)"
@@ -116,9 +296,11 @@ detect_platform() {
Linux)
OS="linux"
;;
+
Darwin)
OS="macos"
;;
+
*)
die "unsupported OS: $os"
;;
@@ -128,28 +310,84 @@ detect_platform() {
x86_64|amd64)
ARCH="x86_64"
;;
+
arm64|aarch64)
ARCH="aarch64"
;;
+
*)
die "unsupported architecture: $arch"
;;
esac
}
+resolve_stable_pointer() {
+ stable="$(
+ fetch_text "$VIX_STABLE_URL" 2>/dev/null |
+ awk '
+ {
+ gsub(/\r/, "", $0)
+ }
+
+ NF {
+ print $1
+ exit
+ }
+ '
+ )" || return 1
+
+ [ -n "$stable" ] || return 1
+ valid_release_tag "$stable" || return 1
+
+ printf "%s" "$stable"
+}
+
+release_is_installable() {
+ tag="$1"
+ base_url="https://github.com/${VIX_REPO}/releases/download/${tag}"
+
+ valid_release_tag "$tag" || return 1
+
+ url_exists "$base_url/$ASSET" || return 1
+ url_exists "$base_url/$ASSET.sha256" || return 1
+
+ return 0
+}
+
resolve_version() {
- if [ "$VIX_VERSION" = "latest" ]; then
- have curl || die "curl is required to resolve latest, or set VIX_VERSION=vX.Y.Z"
+ if [ "$VIX_VERSION" != "latest" ]; then
+ valid_release_tag "$VIX_VERSION" \
+ || die "invalid release version: $VIX_VERSION"
+
+ release_is_installable "$VIX_VERSION" \
+ || die "release $VIX_VERSION is incomplete for $OS/$ARCH"
+
+ printf "%s" "$VIX_VERSION"
+ return
+ fi
- final="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$VIX_REPO/releases/latest")"
- tag="${final##*/}"
+ stable="$(resolve_stable_pointer || true)"
- [ -n "$tag" ] || die "could not resolve latest version"
+ if [ -n "$stable" ]; then
+ if release_is_installable "$stable"; then
+ printf "%s" "$stable"
+ return
+ fi
- printf "%s" "$tag"
+ warn "validated stable release $stable is incomplete for $OS/$ARCH"
else
- printf "%s" "$VIX_VERSION"
+ warn "could not resolve the validated stable release"
+ fi
+
+ if valid_release_tag "$VIX_FALLBACK_VERSION" &&
+ [ "$VIX_FALLBACK_VERSION" != "$stable" ] &&
+ release_is_installable "$VIX_FALLBACK_VERSION"; then
+ step "Falling back to stable release $VIX_FALLBACK_VERSION"
+ printf "%s" "$VIX_FALLBACK_VERSION"
+ return
fi
+
+ die "no installable Vix release found for $OS/$ARCH"
}
verify_checksum() {
@@ -161,29 +399,39 @@ verify_checksum() {
fi
expected="$(
- sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]*\).*/\1/p' "$sha_file" | head -n 1
+ sed -n 's/^\([0-9a-fA-F]\{64\}\).*/\1/p' "$sha_file" |
+ head -n 1 |
+ tr 'A-F' 'a-f'
)"
[ -n "$expected" ] || die "invalid sha256 file"
if have sha256sum; then
- actual="$(sha256sum "$archive" | awk '{print $1}')"
+ actual="$(
+ sha256sum "$archive" |
+ awk '{print $1}' |
+ tr 'A-F' 'a-f'
+ )"
else
- actual="$(shasum -a 256 "$archive" | awk '{print $1}')"
+ actual="$(
+ shasum -a 256 "$archive" |
+ awk '{print $1}' |
+ tr 'A-F' 'a-f'
+ )"
fi
+ [ -n "$actual" ] || die "could not calculate archive sha256"
[ "$expected" = "$actual" ] || die "sha256 mismatch"
}
-
verify_signature() {
archive="$1"
sig_file="$2"
- if ! have minisign; then
- return
- fi
-
- minisign -Vm "$archive" -x "$sig_file" -P "$MINISIGN_PUBKEY" >/dev/null 2>&1 \
+ minisign \
+ -Vm "$archive" \
+ -x "$sig_file" \
+ -P "$MINISIGN_PUBKEY" \
+ >/dev/null 2>&1 \
|| die "signature verification failed"
}
@@ -197,16 +445,21 @@ download_and_verify_asset() {
step "Downloading $asset"
- fetch "$base_url/$asset" "$archive" || die "download failed"
- fetch "$base_url/$asset.sha256" "$sha_file" || die "checksum not found"
+ fetch "$base_url/$asset" "$archive" \
+ || die "failed to download $asset"
+
+ fetch "$base_url/$asset.sha256" "$sha_file" \
+ || die "checksum not found for $asset"
verify_checksum "$archive" "$sha_file"
ok "sha256 verified"
- if fetch "$base_url/$asset.minisig" "$sig_file"; then
- verify_signature "$archive" "$sig_file"
+ if fetch "$base_url/$asset.minisig" "$sig_file" 2>/dev/null; then
if have minisign; then
+ verify_signature "$archive" "$sig_file"
ok "minisign verified"
+ else
+ hint "minisign is not installed; signature verification skipped"
fi
fi
@@ -218,43 +471,123 @@ install_cli() {
extract_dir="$TMP_DIR/cli"
rm -rf "$extract_dir"
- mkdir -p "$extract_dir" "$VIX_INSTALL_BIN_DIR"
+ mkdir -p "$extract_dir"
- step "Installing to $VIX_INSTALL_BIN_DIR/$BIN_NAME"
+ step "Extracting $ASSET"
- tar -xzf "$archive" -C "$extract_dir"
+ tar -xzf "$archive" -C "$extract_dir" \
+ || die "failed to extract $ASSET"
if [ -f "$extract_dir/$BIN_NAME" ]; then
src="$extract_dir/$BIN_NAME"
elif [ -f "$extract_dir/bin/$BIN_NAME" ]; then
src="$extract_dir/bin/$BIN_NAME"
else
- src="$(find "$extract_dir" -type f -name "$BIN_NAME" 2>/dev/null | head -n 1 || true)"
+ src="$(
+ find "$extract_dir" -type f -name "$BIN_NAME" 2>/dev/null |
+ awk 'NR == 1 { print; exit }'
+ )"
fi
[ -n "$src" ] || die "$BIN_NAME not found in archive"
+ [ -f "$src" ] || die "invalid $BIN_NAME executable in archive"
+
+ note_src="$extract_dir/share/vix/note"
+ note_dest="$VIX_INSTALL_SHARE_DIR/vix/note"
+ note_parent="$VIX_INSTALL_SHARE_DIR/vix"
+
+ [ -f "$note_src/index.html" ] \
+ || die "missing Vix Note asset: index.html"
+
+ [ -f "$note_src/assets/note.css" ] \
+ || die "missing Vix Note asset: note.css"
+
+ [ -f "$note_src/assets/note.js" ] \
+ || die "missing Vix Note asset: note.js"
- chmod +x "$src"
- cp "$src" "$VIX_INSTALL_BIN_DIR/$BIN_NAME"
- chmod +x "$VIX_INSTALL_BIN_DIR/$BIN_NAME"
+ mkdir -p "$VIX_INSTALL_BIN_DIR"
+ mkdir -p "$note_parent"
+
+ BIN_STAGE="$VIX_INSTALL_BIN_DIR/.${BIN_NAME}.install.$$"
+ NOTE_STAGE="$note_parent/.note.install.$$"
+
+ rm -f "$BIN_STAGE"
+ rm -rf "$NOTE_STAGE"
+
+ step "Preparing $VIX_INSTALL_BIN_DIR/$BIN_NAME"
+
+ cp "$src" "$BIN_STAGE" \
+ || die "failed to prepare $BIN_NAME executable"
+
+ chmod +x "$BIN_STAGE" \
+ || die "failed to make $BIN_NAME executable"
+
+ if ! "$BIN_STAGE" --version >/dev/null 2>&1; then
+ die "downloaded $BIN_NAME executable failed its version check"
+ fi
+
+ step "Preparing Vix Note assets"
+
+ cp -R "$note_src" "$NOTE_STAGE" \
+ || die "failed to prepare Vix Note assets"
+
+ [ -f "$NOTE_STAGE/index.html" ] \
+ || die "failed to prepare Vix Note index.html"
+
+ [ -f "$NOTE_STAGE/assets/note.css" ] \
+ || die "failed to prepare Vix Note note.css"
+
+ [ -f "$NOTE_STAGE/assets/note.js" ] \
+ || die "failed to prepare Vix Note note.js"
+
+ step "Installing Vix Note assets to $note_dest"
+
+ rm -rf "$note_dest"
+
+ mv "$NOTE_STAGE" "$note_dest" \
+ || die "failed to install Vix Note assets"
+
+ NOTE_STAGE=""
+
+ step "Installing to $VIX_INSTALL_BIN_DIR/$BIN_NAME"
DEST="$VIX_INSTALL_BIN_DIR/$BIN_NAME"
+
+ mv "$BIN_STAGE" "$DEST" \
+ || die "failed to install $BIN_NAME"
+
+ BIN_STAGE=""
+
+ chmod +x "$DEST" \
+ || die "failed to make installed $BIN_NAME executable"
+
+ ok "Vix Note assets installed"
}
detect_platform
+ASSET="vix-${OS}-${ARCH}.tar.gz"
+
TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t vix)"
-cleanup() {
- rm -rf "$TMP_DIR"
-}
-trap cleanup EXIT INT TERM
+
+trap cleanup EXIT HUP INT TERM
+
+printf " %s▲%s %s%sVix.cpp%s installer\n" \
+ "$C_CYAN" \
+ "$C_RESET" \
+ "$C_BOLD" \
+ "$C_GREEN" \
+ "$C_RESET" \
+ >&2
+
+printf " %s------------------------------------%s\n" \
+ "$C_DIM" \
+ "$C_RESET" \
+ >&2
TAG="$(resolve_version)"
BASE_URL="https://github.com/${VIX_REPO}/releases/download/${TAG}"
-ASSET="vix-${OS}-${ARCH}.tar.gz"
-printf " ▲ %sVix.cpp%s %sinstaller%s\n" "$C_BOLD" "$C_RESET" "$C_DIM" "$C_RESET" >&2
-printf " ------------------------------------\n" >&2
printf " version %s\n" "$TAG" >&2
printf " platform %s/%s\n" "$OS" "$ARCH" >&2
printf "\n" >&2
@@ -272,6 +605,7 @@ case ":$PATH:" in
hint "run: vix upgrade --check"
hint "sdk: vix upgrade --sdk list"
;;
+
*)
hint "add $VIX_INSTALL_BIN_DIR to PATH"
hint "then run: vix upgrade --sdk list"
diff --git a/vix-site/public/releases/stable.txt b/vix-site/public/releases/stable.txt
new file mode 100644
index 00000000..5d92048c
--- /dev/null
+++ b/vix-site/public/releases/stable.txt
@@ -0,0 +1 @@
+v2.7.8
diff --git a/vix-site/src/components/home/AppModulesSection.vue b/vix-site/src/components/home/AppModulesSection.vue
new file mode 100644
index 00000000..52204f3c
--- /dev/null
+++ b/vix-site/src/components/home/AppModulesSection.vue
@@ -0,0 +1,396 @@
+
+
+ A module is not a separate service by default, and it is not a
+ package by default. It is a feature boundary inside the application:
+ public headers, private implementation, tests, metadata, optional
+ migrations, and its own build target.
+
+ The application keeps the startup flow stable. Modules own
+ feature-specific routes and implementation, while the active module
+ graph remains visible from {{ feature.text }}vix.app.
+
{{ item.note }}
+ +- Drogon and Crow answer a focused question: - how do I build a C++ web service? - Vix.cpp answers a wider one: - how do I build, run, test, package, extend, and maintain a real - native C++ application? + Drogon is slightly ahead in raw throughput, but Vix.cpp stays very + close while showing the best p99 latency in this test. Compared to + Crow, Vix.cpp is faster and keeps a lower tail latency.
+ C++ errors can be technically correct and still difficult to act on. + Vix parses compiler output, looks for the user file first, prints a + focused code frame, and adds a hint that points to the next thing to + check. +
+ ++ The same diagnostic layer also helps with build and runtime + failures: missing headers, stale CMake caches, linker errors, + sanitizer output, invalid frees, use-after-free reports, assertions, + and ports already in use. +
+{{ item.text }}
+{{ activeTab.kicker }}
+{{ activeTab.kicker }}
+ A production release is not only a build command. The application + has to restart correctly, respond locally, work behind the public + proxy, and leave useful logs when something fails. +
+ +
+ Vix reads that workflow from vix.json. A generated Vix
+ backend can use it directly, and an existing C++ service can use the
+ same production metadata without changing its source layout.
+
{{ item.text }}
++ The renderer is made for projects that want server-rendered HTML + directly from the Vix backend. A web application can keep its + routes, controllers, views, public assets, and runtime configuration + inside the C++ project without starting with a separate frontend + framework. +
+ ++ Templates are parsed into a real structure, not treated as simple + string replacement. Vix understands variables, member access, + arithmetic and logical expressions, filters, includes, inheritance, + blocks, cache signatures, and automatic HTML escaping. +
+{{ item.text }}
+
+ Some failures are hard to reproduce because the command, working
+ directory, arguments, environment, logs, and exit status disappear
+ after the process ends. Replay keeps that execution context under
+ .vix/runs/ so the run can be inspected or launched
+ again later.
+
+ It fits naturally with Vix diagnostics. Diagnostics help explain + what failed; replay helps you run the same failure again without + rebuilding the command from memory. +
+{{ item.text }}
+
+ Running vix without a command opens the REPL by
+ default. It gives C++ projects a small scratchpad for quick checks,
+ simple runtime helpers, and experiments that are too small to start
+ as a full source file.
+
+ When an idea needs real C++, Reply can switch into snippet mode and + send the code through the normal Vix run pipeline. It is not a fake + interpreter; the snippet still goes through the compiler and the + same diagnostics used by the project workflow. +
+
+ A template is not only a directory layout. It decides which files
+ are generated, which workflow is suggested after creation, how
+ vix.app or CMake is used, which runtime resources are
+ copied, and how the project can grow.
+
+ Application, backend, web, Vue, and game projects are + application-oriented. The library template is different: it starts + from reusable C++ code, public headers, examples, tests, and a + package-style build shape. +
++ The manifest is not meant to become a large build script. It should + describe the application shape clearly enough that another developer + can open the project root and understand what is being built. +
+ +
+ Existing CMake projects still keep their build behavior. When a root
+ CMakeLists.txt exists, Vix uses it first. When the
+ project is a pure vix.app application, Vix generates
+ the internal build input under .vix/generated/app/.
+
{{ item.text }}
+