feat(installer): self-select release download source (#176)

This commit is contained in:
Laodouuu authored and GitHub committed 2026-08-04 18:44:30 +08:00
1 parent 747077cabb
commit 38df4e62ee
7 files changed
+347 -50

No files matched your search

+86 -13
View File
@@ -3,9 +3,11 @@
# irm https://penguin.ooo/install.ps1 | iex
#
# Options:
# $env:PENGUIN_VERSION = "vX.Y.Z" pin a version (same as -Version vX.Y.Z); default is the latest Release
# $env:PENGUIN_VERSION = "vX.Y.Z" choose a version (same as -Version vX.Y.Z); a published Release
# installer defaults to its own version, an unstamped source copy to latest
# $env:PENGUIN_INSTALL_DIR = "<dir>" install dir; default $env:USERPROFILE\.penguin
# $env:PENGUIN_ARCHIVE = "<file>" install a local Release zip without network access (same as -ArchivePath)
# $env:PENGUIN_DOWNLOAD_SOURCE = "auto|oss|github" choose the online source; default auto (OSS, then same-version GitHub)
# $env:PENGUIN_DOWNLOAD_BASE_URL = "https://..." exact online asset directory selected by the stable forwarder
# $env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = "https://..." same-version fallback asset directory
#
@@ -35,8 +37,15 @@ $ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue" # Invoke-WebRequest progress rendering slows downloads massively on PS 5.1
$Repo = "https://github.com/Prism-Shadow/penguin-harness"
$OssOrigin = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com"
$OssReleaseRoot = "$OssOrigin/releases"
$GitHubReleaseRoot = "$Repo/releases/download"
$GitHubLatestBase = "$Repo/releases/latest/download"
$Asset = "penguin-win32-x64.zip"
$PayloadName = "payload.zip"
# The release workflow replaces this token with the immutable tag before publishing both the
# standalone installer and the copy sealed inside the Windows bundle.
$EmbeddedReleaseVersion = "__PENGUIN_RELEASE_VERSION__"
function Fail([string]$Message) {
# `throw` rather than `exit`: the penguin.ooo forwarder runs this installer as an in-memory
@@ -76,6 +85,32 @@ function Assert-HttpsUrl([string]$Name, [string]$Value) {
}
}
function Test-ReleaseTag([string]$Value) {
return $Value -match '^v[0-9A-Za-z][0-9A-Za-z._-]*$'
}
function Get-OssLatestTag([string]$ManifestPath) {
Remove-Item -LiteralPath $ManifestPath -Force -ErrorAction SilentlyContinue
try {
Invoke-WebRequest -Uri "$OssOrigin/latest.json" -OutFile $ManifestPath -UseBasicParsing -TimeoutSec 8 | Out-Null
} catch {
Remove-Item -LiteralPath $ManifestPath -Force -ErrorAction SilentlyContinue
return ""
}
try {
$Manifest = [IO.File]::ReadAllText($ManifestPath, [Text.UTF8Encoding]::new($false)) | ConvertFrom-Json
$CandidateTag = [string]$Manifest.tag
$CandidateBase = ([string]$Manifest.releaseBaseUrl).TrimEnd('/')
if ([int]$Manifest.schemaVersion -eq 1 -and
(Test-ReleaseTag $CandidateTag) -and
$CandidateBase -eq "$OssReleaseRoot/$CandidateTag") {
return $CandidateTag
}
} catch {
}
return ""
}
function Get-DownloadSourceLabel([string]$BaseUrl) {
try { $HostName = ([Uri]$BaseUrl).Host } catch { return "configured mirror" }
if ($HostName -like "*.aliyuncs.com") { return "OSS mirror" }
@@ -141,6 +176,11 @@ $DownloadFallbackBaseUrl = if ($env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL) {
} else {
""
}
$SourceMode = if ($env:PENGUIN_DOWNLOAD_SOURCE) {
$env:PENGUIN_DOWNLOAD_SOURCE.ToLowerInvariant()
} else {
"auto"
}
# An extracted installer bundle keeps install.cmd, this script, payload.zip and its checksum
# together. `$PSScriptRoot` is empty for the documented `irm ... | iex` path, so online installs
# do not accidentally pick up an unrelated archive from the caller's current directory.
@@ -151,9 +191,19 @@ if (-not $ArchivePath -and $PSScriptRoot) {
if ($ArchivePath -and $Version) {
Fail "-ArchivePath/PENGUIN_ARCHIVE cannot be combined with -Version/PENGUIN_VERSION"
}
if ($Version -and $Version -notmatch '^v[0-9A-Za-z][0-9A-Za-z._-]*$') {
if ($Version -and -not (Test-ReleaseTag $Version)) {
Fail "invalid release version: $Version"
}
if ($SourceMode -notin @("auto", "oss", "github")) {
Fail "PENGUIN_DOWNLOAD_SOURCE must be auto, oss, or github"
}
$ResolvedReleaseVersion = if ($Version) {
$Version
} elseif (Test-ReleaseTag $EmbeddedReleaseVersion) {
$EmbeddedReleaseVersion
} else {
""
}
if ($DownloadBaseUrl) {
Assert-HttpsUrl "PENGUIN_DOWNLOAD_BASE_URL" $DownloadBaseUrl
}
@@ -176,14 +226,10 @@ try {
# .NET builds where the enum is immutable already default to TLS 1.2+.
}
# --- Download (latest GitHub Release by default; the stable forwarder may select exact mirrors) ---
if ($DownloadBaseUrl) {
$BaseUrl = $DownloadBaseUrl
} elseif ($Version) {
$BaseUrl = "$Repo/releases/download/$Version"
} else {
$BaseUrl = "$Repo/releases/latest/download"
}
# --- Download. Explicit forwarder/configured URLs win. Otherwise a stamped Release installer
# uses its own immutable version: auto prefers OSS and falls back only to the same GitHub
# tag. An unstamped source-tree installer resolves latest.json first so it also locks one
# version before downloading assets. ---
$Tmp = Join-Path ([IO.Path]::GetTempPath()) "penguin-install-$PID"
if (Test-Path $Tmp) { Remove-Item -Recurse -Force $Tmp }
New-Item -ItemType Directory -Path $Tmp | Out-Null
@@ -203,14 +249,41 @@ try {
$ArchiveName = [IO.Path]::GetFileName($ZipPath)
Write-Host "Using local archive $ZipPath ..."
} else {
$BaseUrl = ""
$FallbackBaseUrl = $DownloadFallbackBaseUrl
if ($DownloadBaseUrl) {
$BaseUrl = $DownloadBaseUrl
} elseif ($SourceMode -eq "github") {
$BaseUrl = if ($ResolvedReleaseVersion) {
"$GitHubReleaseRoot/$ResolvedReleaseVersion"
} else {
$GitHubLatestBase
}
} else {
$SelectedTag = $ResolvedReleaseVersion
if (-not $SelectedTag) {
$SelectedTag = Get-OssLatestTag (Join-Path $Tmp "latest.json")
}
if ($SelectedTag) {
$BaseUrl = "$OssReleaseRoot/$SelectedTag"
if ($SourceMode -eq "auto" -and -not $FallbackBaseUrl) {
$FallbackBaseUrl = "$GitHubReleaseRoot/$SelectedTag"
}
} elseif ($SourceMode -eq "oss") {
Fail "the OSS mirror is unavailable or its release metadata is invalid."
} else {
$BaseUrl = $GitHubLatestBase
}
}
# Online: download the canonical bundle; the published checksum is mandatory.
$ZipPath = Join-Path $Tmp $Asset
$ArchiveName = $Asset
$ShaPath = Join-Path $Tmp "$Asset.sha256"
if (-not (Get-ReleasePair $BaseUrl $ZipPath $ShaPath)) {
if ($DownloadFallbackBaseUrl -and $DownloadFallbackBaseUrl -ne $BaseUrl) {
Write-Host "Primary download source unavailable; trying $(Get-DownloadSourceLabel $DownloadFallbackBaseUrl) ..."
if (-not (Get-ReleasePair $DownloadFallbackBaseUrl $ZipPath $ShaPath)) {
if ($FallbackBaseUrl -and $FallbackBaseUrl -ne $BaseUrl) {
Write-Host "Primary download source unavailable; trying $(Get-DownloadSourceLabel $FallbackBaseUrl) ..."
if (-not (Get-ReleasePair $FallbackBaseUrl $ZipPath $ShaPath)) {
Fail "download failed from both the primary source and its fallback. Check your network, then retry."
}
} else {