feat(release): add Alibaba Cloud OSS distribution (#166)

This commit is contained in:
Laodouuu
2026-08-03 21:04:24 +08:00
committed by GitHub
parent 24cea8c511
commit 2e8389f438
13 changed files with 1199 additions and 57 deletions
+60
View File
@@ -0,0 +1,60 @@
name: Test Alibaba Cloud OSS publishing
on:
workflow_dispatch:
jobs:
staging:
name: Verify GitHub OIDC staging access
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment:
name: oss-staging
steps:
- uses: actions/checkout@v5
- name: Validate OSS environment configuration
env:
ALIYUN_OIDC_PROVIDER_ARN: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
ALIYUN_ROLE_ARN: ${{ vars.ALIYUN_ROLE_ARN }}
OSS_BUCKET: ${{ vars.OSS_BUCKET }}
OSS_REGION: ${{ vars.OSS_REGION }}
OSS_ENDPOINT: ${{ vars.OSS_ENDPOINT }}
OSS_PUBLIC_BASE_URL: ${{ vars.OSS_PUBLIC_BASE_URL }}
run: |
set -eu
for name in ALIYUN_OIDC_PROVIDER_ARN ALIYUN_ROLE_ARN OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
eval "value=\${$name:-}"
if [ -z "$value" ]; then
echo "error: $name is not configured in the oss-staging environment" >&2
exit 1
fi
done
- name: Download and verify ossutil
run: |
sh scripts/install-ossutil.sh "$RUNNER_TEMP/ossutil-bin"
echo "$RUNNER_TEMP/ossutil-bin" >> "$GITHUB_PATH"
- name: Exchange GitHub OIDC token for Alibaba Cloud credentials
id: aliyun
uses: aliyun/configure-aliyun-credentials-action@1e5248c8d5d93a8781ac344a68e19a43341e79e6
with:
oidc-provider-arn: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
role-to-assume: ${{ vars.ALIYUN_ROLE_ARN }}
role-session-name: penguin-oss-staging-${{ github.run_id }}
role-session-expiration: 1800
audience: github-actions
- name: Verify staging access boundaries
env:
OSS_ACCESS_KEY_ID: ${{ steps.aliyun.outputs['aliyun-access-key-id'] }}
OSS_ACCESS_KEY_SECRET: ${{ steps.aliyun.outputs['aliyun-access-key-secret'] }}
OSS_SESSION_TOKEN: ${{ steps.aliyun.outputs['aliyun-security-token'] }}
OSS_BUCKET: ${{ vars.OSS_BUCKET }}
OSS_REGION: ${{ vars.OSS_REGION }}
OSS_ENDPOINT: ${{ vars.OSS_ENDPOINT }}
OSS_PUBLIC_BASE_URL: ${{ vars.OSS_PUBLIC_BASE_URL }}
run: sh scripts/test-oss-staging.sh
+100 -4
View File
@@ -1,8 +1,8 @@
# Release: tag v* -> build the one-line install artifacts and publish a GitHub Release.
# Releases are immutable: once published, assets can never be replaced. A tiny check-release
# job therefore gates the release job on the tag's Release not existing yet — dispatching an
# already-released tag skips the build/upload entirely and only re-runs npm publishing.
# Two parallel jobs (the release job is gated on the existence check):
# already-released tag skips the GitHub build/upload while still retrying the OSS mirror and npm publishing.
# Release jobs (the release job is gated on the existence check):
# - release: build the monorepo -> pnpm deploy a production CLI dir -> assemble penguin/ (bin + lib + web)
# -> one program payload per target (four platform payloads bundling the official Node runtime,
# a win-x64 payload with runtime + MinGit, and a runtime-less universal payload) -> wrap each
@@ -31,6 +31,10 @@
# that failed mid-chain can be re-run as-is after fixing the config.
# npm publishing lives here rather than a separate `on: release: published` workflow: the Release is created
# by this workflow's GITHUB_TOKEN, and GitHub won't trigger other workflows' release events from that.
# - mirror-oss: download the exact GitHub Release assets, verify their checksums, then mirror the same bytes
# to immutable releases/<tag>/ keys in Alibaba Cloud OSS through GitHub OIDC. The GitHub Environment
# `oss-production` supplies the provider/role ARNs and OSS settings. latest.json is uploaded last and only
# when the tag is still GitHub's current latest Release. Manual retries also work after a Release exists.
name: Release
on:
@@ -54,7 +58,7 @@ env:
jobs:
# Skip the build/upload when the tag's Release already exists (immutable releases forbid
# replacing assets, so re-uploading can only fail; npm publishing is idempotent on its own).
# replacing assets, so re-uploading can only fail; OSS mirroring and npm publishing are idempotent).
check-release:
runs-on: ubuntu-latest
permissions:
@@ -278,7 +282,7 @@ jobs:
- name: Generate SHA256SUMS
run: |
cd dist-artifacts
sha256sum *.tar.gz *.zip > SHA256SUMS
sha256sum -- *.tar.gz *.zip > SHA256SUMS
# Release notes come from changelog/<version>/RELEASE.md, written during release preparation and
# committed BEFORE the tag (the release job runs on the tag's checkout, so a file added afterwards
@@ -319,6 +323,98 @@ jobs:
install.sh
install.ps1
mirror-oss:
name: Mirror GitHub Release to Alibaba Cloud OSS
needs: [check-release, release]
if: >-
${{ always() && needs.check-release.result == 'success' &&
(needs.release.result == 'success' || needs.release.result == 'skipped') }}
runs-on: ubuntu-latest
concurrency:
group: penguin-oss-production
cancel-in-progress: false
permissions:
contents: read
id-token: write
environment:
name: oss-production
url: ${{ vars.OSS_PUBLIC_BASE_URL }}
steps:
# On workflow_dispatch, use the selected branch's current mirror scripts while downloading
# the requested tag's immutable Release assets. A tag push naturally checks out that tag.
- uses: actions/checkout@v5
- name: Validate OSS environment configuration
env:
ALIYUN_OIDC_PROVIDER_ARN: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
ALIYUN_ROLE_ARN: ${{ vars.ALIYUN_ROLE_ARN }}
OSS_BUCKET: ${{ vars.OSS_BUCKET }}
OSS_REGION: ${{ vars.OSS_REGION }}
OSS_ENDPOINT: ${{ vars.OSS_ENDPOINT }}
OSS_PUBLIC_BASE_URL: ${{ vars.OSS_PUBLIC_BASE_URL }}
run: |
set -eu
for name in ALIYUN_OIDC_PROVIDER_ARN ALIYUN_ROLE_ARN OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
eval "value=\${$name:-}"
if [ -z "$value" ]; then
echo "error: $name is not configured in the oss-production environment" >&2
exit 1
fi
done
- name: Download and verify ossutil
run: |
sh scripts/install-ossutil.sh "$RUNNER_TEMP/ossutil-bin"
echo "$RUNNER_TEMP/ossutil-bin" >> "$GITHUB_PATH"
# Pinned v1 commit. audience must match the client ID configured on the Alibaba Cloud
# OIDC provider; this project deliberately uses `github-actions`.
- name: Exchange GitHub OIDC token for Alibaba Cloud credentials
id: aliyun
uses: aliyun/configure-aliyun-credentials-action@1e5248c8d5d93a8781ac344a68e19a43341e79e6
with:
oidc-provider-arn: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
role-to-assume: ${{ vars.ALIYUN_ROLE_ARN }}
role-session-name: penguin-oss-${{ github.run_id }}
role-session-expiration: 1800
audience: github-actions
- name: Download exact GitHub Release assets
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
run: |
mkdir -p release-assets
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir release-assets
- name: Determine whether the tag is the latest Release
id: latest
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
run: |
LATEST_TAG="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)"
if [ "$TAG" = "$LATEST_TAG" ]; then
echo "update=true" >> "$GITHUB_OUTPUT"
else
echo "update=false" >> "$GITHUB_OUTPUT"
echo "$TAG will be mirrored without replacing latest.json (current latest: $LATEST_TAG)."
fi
- name: Mirror and verify OSS objects
env:
TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
GH_TOKEN: ${{ github.token }}
OSS_ACCESS_KEY_ID: ${{ steps.aliyun.outputs['aliyun-access-key-id'] }}
OSS_ACCESS_KEY_SECRET: ${{ steps.aliyun.outputs['aliyun-access-key-secret'] }}
OSS_SESSION_TOKEN: ${{ steps.aliyun.outputs['aliyun-security-token'] }}
OSS_BUCKET: ${{ vars.OSS_BUCKET }}
OSS_REGION: ${{ vars.OSS_REGION }}
OSS_ENDPOINT: ${{ vars.OSS_ENDPOINT }}
OSS_PUBLIC_BASE_URL: ${{ vars.OSS_PUBLIC_BASE_URL }}
run: sh scripts/publish-release-to-oss.sh release-assets "$TAG" "${{ steps.latest.outputs.update }}"
publish-npm:
name: Publish npm packages
runs-on: ubuntu-latest
+66 -12
View File
@@ -6,6 +6,8 @@
# $env:PENGUIN_VERSION = "vX.Y.Z" pin a version (same as -Version vX.Y.Z); default is the latest Release
# $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_BASE_URL = "https://..." exact online asset directory selected by the stable forwarder
# $env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = "https://..." same-version fallback asset directory
#
# Each Release attaches exactly one Windows artifact: penguin-win32-x64.zip, a shallow installer
# bundle holding install.cmd, this script, the program payload (payload.zip) and the payload's
@@ -67,6 +69,38 @@ function Assert-Sha256([string]$FilePath, [string]$ShaPath, [string]$Label) {
Write-Host "$Label checksum OK."
}
function Assert-HttpsUrl([string]$Name, [string]$Value) {
try { $Uri = [Uri]$Value } catch { Fail "$Name is not a valid URL" }
if (-not $Uri.IsAbsoluteUri -or $Uri.Scheme -ne "https") {
Fail "$Name must be an absolute HTTPS URL"
}
}
function Get-DownloadSourceLabel([string]$BaseUrl) {
try { $HostName = ([Uri]$BaseUrl).Host } catch { return "configured mirror" }
if ($HostName -like "*.aliyuncs.com") { return "OSS mirror" }
if ($HostName -eq "github.com") { return "GitHub" }
return "configured mirror"
}
function Get-ReleasePair(
[string]$BaseUrl,
[string]$ZipPath,
[string]$ShaPath
) {
$Label = Get-DownloadSourceLabel $BaseUrl
Write-Host "Downloading $Asset from $Label ..."
Remove-Item -LiteralPath $ZipPath, $ShaPath -Force -ErrorAction SilentlyContinue
try {
Invoke-WebRequest -Uri "$BaseUrl/$Asset" -OutFile $ZipPath -UseBasicParsing
Invoke-WebRequest -Uri "$BaseUrl/$Asset.sha256" -OutFile $ShaPath -UseBasicParsing
return $true
} catch {
Remove-Item -LiteralPath $ZipPath, $ShaPath -Force -ErrorAction SilentlyContinue
return $false
}
}
function Restore-PreviousInstall(
[string]$InstallDir,
[string]$OldDir,
@@ -97,6 +131,16 @@ if (-not $InstallDir) {
if (-not $ArchivePath) {
$ArchivePath = if ($env:PENGUIN_ARCHIVE) { $env:PENGUIN_ARCHIVE } else { "" }
}
$DownloadBaseUrl = if ($env:PENGUIN_DOWNLOAD_BASE_URL) {
$env:PENGUIN_DOWNLOAD_BASE_URL.TrimEnd('/')
} else {
""
}
$DownloadFallbackBaseUrl = if ($env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL) {
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL.TrimEnd('/')
} else {
""
}
# 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.
@@ -107,6 +151,15 @@ 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._-]*$') {
Fail "invalid release version: $Version"
}
if ($DownloadBaseUrl) {
Assert-HttpsUrl "PENGUIN_DOWNLOAD_BASE_URL" $DownloadBaseUrl
}
if ($DownloadFallbackBaseUrl) {
Assert-HttpsUrl "PENGUIN_DOWNLOAD_FALLBACK_BASE_URL" $DownloadFallbackBaseUrl
}
# --- Platform preconditions: 64-bit Windows; the only Windows package is x64 (ARM64 runs it emulated) ---
if (-not [Environment]::Is64BitOperatingSystem) {
@@ -123,8 +176,10 @@ try {
# .NET builds where the enum is immutable already default to TLS 1.2+.
}
# --- Download (latest Release by default; PENGUIN_VERSION pins a version) ---
if ($Version) {
# --- 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"
@@ -149,19 +204,18 @@ try {
Write-Host "Using local archive $ZipPath ..."
} else {
# Online: download the canonical bundle; the published checksum is mandatory.
Write-Host "Downloading $BaseUrl/$Asset ..."
$ZipPath = Join-Path $Tmp $Asset
try {
Invoke-WebRequest -Uri "$BaseUrl/$Asset" -OutFile $ZipPath -UseBasicParsing
} catch {
Fail "download failed. Check the version tag and your network, then retry. ($($_.Exception.Message))"
}
$ArchiveName = $Asset
$ShaPath = Join-Path $Tmp "$Asset.sha256"
try {
Invoke-WebRequest -Uri "$BaseUrl/$Asset.sha256" -OutFile $ShaPath -UseBasicParsing
} catch {
Fail "checksum download failed. Check the version tag and your network, then retry. ($($_.Exception.Message))"
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)) {
Fail "download failed from both the primary source and its fallback. Check your network, then retry."
}
} else {
Fail "download failed from $(Get-DownloadSourceLabel $BaseUrl). Check the version tag and your network, then retry."
}
}
Assert-Sha256 $ZipPath $ShaPath "Bundle"
}
+67 -6
View File
@@ -7,6 +7,8 @@
# PENGUIN_VERSION=vX.Y.Z pin a version (same as --version vX.Y.Z); default is the latest Release
# PENGUIN_INSTALL_DIR=<dir> install dir; default ~/.penguin
# PENGUIN_ARCHIVE=<file> install a local Release archive without network access (same as --archive <file>)
# PENGUIN_DOWNLOAD_BASE_URL=<url> exact online asset directory selected by the stable forwarder
# PENGUIN_DOWNLOAD_FALLBACK_BASE_URL=<url> same-version fallback asset directory
# --universal install the universal package (no bundled Node runtime; needs system Node >= 24)
#
# Each Release attaches exactly one artifact per target: penguin-<target>.tar.gz, a shallow
@@ -29,6 +31,8 @@ INSTALL_DIR="${PENGUIN_INSTALL_DIR:-$HOME/.penguin}"
BIN_DIR="$HOME/.local/bin"
UNIVERSAL=0
ARCHIVE="${PENGUIN_ARCHIVE:-}"
DOWNLOAD_BASE_URL="${PENGUIN_DOWNLOAD_BASE_URL:-}"
DOWNLOAD_FALLBACK_BASE_URL="${PENGUIN_DOWNLOAD_FALLBACK_BASE_URL:-}"
PAYLOAD_NAME="payload.tar.gz"
fail() {
@@ -36,6 +40,31 @@ fail() {
exit 1
}
validate_https_url() {
case "$2" in
https://*) ;;
*) fail "$1 must be an absolute HTTPS URL" ;;
esac
}
validate_release_tag() {
case "$1" in
v[0-9A-Za-z]* ) ;;
*) fail "invalid release version: $1" ;;
esac
case "$1" in
*[!0-9A-Za-z._-]*) fail "invalid release version: $1" ;;
esac
}
download_source_label() {
case "$1" in
https://*.aliyuncs.com/*) printf '%s\n' "OSS mirror" ;;
https://github.com/*) printf '%s\n' "GitHub" ;;
*) printf '%s\n' "configured mirror" ;;
esac
}
# --- Parse args (also passable via curl | sh -s -- --universal) ---
while [ $# -gt 0 ]; do
case "$1" in
@@ -79,6 +108,17 @@ ASSET="penguin-$TARGET.tar.gz"
if [ -n "$ARCHIVE" ] && [ -n "$VERSION" ]; then
fail "--archive/PENGUIN_ARCHIVE cannot be combined with --version/PENGUIN_VERSION"
fi
if [ -n "$VERSION" ]; then
validate_release_tag "$VERSION"
fi
if [ -n "$DOWNLOAD_BASE_URL" ]; then
DOWNLOAD_BASE_URL="${DOWNLOAD_BASE_URL%/}"
validate_https_url PENGUIN_DOWNLOAD_BASE_URL "$DOWNLOAD_BASE_URL"
fi
if [ -n "$DOWNLOAD_FALLBACK_BASE_URL" ]; then
DOWNLOAD_FALLBACK_BASE_URL="${DOWNLOAD_FALLBACK_BASE_URL%/}"
validate_https_url PENGUIN_DOWNLOAD_FALLBACK_BASE_URL "$DOWNLOAD_FALLBACK_BASE_URL"
fi
# --- Universal package precheck: system Node >= 24 (platform packages bundle the runtime, so exempt) ---
if [ "$UNIVERSAL" -eq 1 ]; then
@@ -184,6 +224,21 @@ verify_sha256() {
echo "$3 checksum OK."
}
# Downloads the bundle and its checksum as a pair. Transport failures may try a same-version
# fallback; checksum failures are handled afterwards and always abort rather than being hidden
# by a different source.
download_release_pair() {
drp_base="$1"
drp_label="$(download_source_label "$drp_base")"
echo "Downloading $ASSET from $drp_label ..."
rm -f "$ARCHIVE_PATH" "$TMP/$ASSET.sha256"
curl -fSL --progress-bar "$drp_base/$ASSET" -o "$ARCHIVE_PATH" \
|| return 1
curl -fsSL "$drp_base/$ASSET.sha256" -o "$TMP/$ASSET.sha256" \
|| return 1
return 0
}
# --- Resolve the program payload. Three entries converge on PAYLOAD_PATH:
# (a) bundled offline: this script sits next to payload.tar.gz in an extracted bundle;
# (b) --archive <file>: a local installer bundle, or a payload/legacy program archive;
@@ -231,18 +286,24 @@ elif [ -n "$ARCHIVE" ]; then
echo "Using local archive $ARCHIVE_PATH ..."
else
# (c) Online: download the canonical bundle; the published checksum is mandatory.
if [ -n "$VERSION" ]; then
if [ -n "$DOWNLOAD_BASE_URL" ]; then
BASE_URL="$DOWNLOAD_BASE_URL"
elif [ -n "$VERSION" ]; then
BASE_URL="$REPO/releases/download/$VERSION"
else
BASE_URL="$REPO/releases/latest/download"
fi
ARCHIVE_PATH="$TMP/$ASSET"
ARCHIVE_NAME="$ASSET"
echo "Downloading $BASE_URL/$ASSET ..."
curl -fSL --progress-bar "$BASE_URL/$ASSET" -o "$ARCHIVE_PATH" \
|| fail "download failed. Check the version tag and your network, then retry."
curl -fsSL "$BASE_URL/$ASSET.sha256" -o "$TMP/$ASSET.sha256" \
|| fail "checksum download failed. Check the version tag and your network, then retry."
if ! download_release_pair "$BASE_URL"; then
if [ -n "$DOWNLOAD_FALLBACK_BASE_URL" ] && [ "$DOWNLOAD_FALLBACK_BASE_URL" != "$BASE_URL" ]; then
echo "Primary download source unavailable; trying $(download_source_label "$DOWNLOAD_FALLBACK_BASE_URL") ..."
download_release_pair "$DOWNLOAD_FALLBACK_BASE_URL" \
|| fail "download failed from both the primary source and its fallback. Check your network, then retry."
else
fail "download failed from $(download_source_label "$BASE_URL"). Check the version tag and your network, then retry."
fi
fi
verify_sha256 "$ARCHIVE_PATH" "$TMP/$ASSET.sha256" "Bundle"
fi
+3
View File
@@ -19,6 +19,8 @@ curl -fsSL https://penguin.ooo/install.sh | sh
The script downloads the matching `penguin-{linux,darwin}-{x64,arm64}.tar.gz` — the canonical installer bundle, sealing the program payload (with an official Node.js runtime), the payload's SHA256 checksum and this same installer. The download is verified against its published `.sha256`, then the sealed payload checksum is verified again before anything is staged. Other POSIX platforms do **not** fall back automatically: the script exits and asks you to install Node.js >= 24 and re-run with `--universal`, which selects the runtime-less `penguin-universal.tar.gz` bundle (Windows is served by its own installer below, not by `--universal`).
The stable entry point defaults to `PENGUIN_DOWNLOAD_SOURCE=auto`: it prefers an immutable OSS release directory only after that release has been completely uploaded and verified, then falls back to the matching GitHub Release if the metadata or download is unavailable. Set the variable to `oss` or `github` to force either source. Normal installer output names the source without printing the mirror's full URL.
On Windows (PowerShell):
```powershell
@@ -62,6 +64,7 @@ The extracted bundle keeps the installer, the program payload (`payload.tar.gz`
| Install dir | `~/.penguin` by default; override with the `PENGUIN_INSTALL_DIR` env var |
| Command entry | A symlink `~/.local/bin/penguin` is created (the script warns if `~/.local/bin` is not on PATH) |
| Version pin | `PENGUIN_VERSION=vX.Y.Z` env var, or the `--version vX.Y.Z` script flag; defaults to the latest Release |
| Download source | `PENGUIN_DOWNLOAD_SOURCE=auto` (default), `oss`, or `github`; auto prefers OSS and falls back to the same GitHub version |
| Local archive | `PENGUIN_ARCHIVE=<file>` or `--archive <file>`; accepts a Release bundle (self-verifying via its sealed payload checksum) or a payload/legacy program archive with an adjacent `<file>.sha256` (renamed legacy files may use the platform asset's canonical `.sha256`) |
| Integrity check | Always on: online downloads are verified against the published `.sha256`, and bundle payloads against the checksum sealed inside the bundle |
| Upgrade | Re-run the install script; files are swapped atomically |
+3
View File
@@ -19,6 +19,8 @@ curl -fsSL https://penguin.ooo/install.sh | sh
脚本按平台下载 `penguin-{linux,darwin}-{x64,arm64}.tar.gz`——即标准安装包:包内封入程序负载(捆绑官方 Node.js 运行时)、负载的 SHA256 校验文件与同一个安装器。下载后先对照 Release 发布的 `.sha256` 校验外层,再校验包内封入的负载 checksum,然后才进入暂存安装。其他 POSIX 平台**不会自动回退**:脚本会退出并提示先安装 Node.js >= 24、再携带 `--universal` 重新执行,改用不含运行时的 `penguin-universal.tar.gz` 安装包(Windows 使用下方专属安装器,而不是 `--universal`)。
稳定入口默认使用 `PENGUIN_DOWNLOAD_SOURCE=auto`:优先选择已完整上传并验证的 OSS 不可变版本目录;元数据或下载不可用时,回退到同一版本的 GitHub Release。也可以将该变量设为 `oss` 或 `github` 来强制指定来源。安装器只显示来源名称,不在常规输出中打印镜像的完整 URL。
在 Windows(PowerShell)上执行:
```powershell
@@ -62,6 +64,7 @@ Linux / macOS 上执行:
| 安装目录 | 默认 `~/.penguin`,可用环境变量 `PENGUIN_INSTALL_DIR` 覆盖 |
| 命令入口 | 创建符号链接 `~/.local/bin/penguin`(若 `~/.local/bin` 不在 PATH 上,脚本会给出提示) |
| 版本固定 | 环境变量 `PENGUIN_VERSION=vX.Y.Z`,或脚本参数 `--version vX.Y.Z`;默认安装最新 Release |
| 下载来源 | `PENGUIN_DOWNLOAD_SOURCE=auto`(默认)、`oss` 或 `github`;自动模式优先 OSS,并按同一版本回退到 GitHub |
| 本地压缩包 | `PENGUIN_ARCHIVE=<file>` 或 `--archive <file>`;接受 Release 安装包(凭包内封入的负载 checksum 自校验),或旁边带 `<file>.sha256` 的负载 / 旧版程序压缩包(重命名的旧版文件可用平台标准名称的 `.sha256`) |
| 完整性校验 | 始终进行:在线下载对照发布的 `.sha256` 校验,安装包负载对照包内封入的 checksum 校验 |
| 升级 | 重新执行安装脚本即可,文件原子替换 |
+154 -26
View File
@@ -1,33 +1,161 @@
# https://penguin.ooo/install.ps1 - PenguinHarness installer entry point for Windows.
#
# GitHub Pages cannot serve HTTP redirects, so this thin forwarder IS the
# stable install URL: it fetches the real installer attached to the latest
# GitHub release and runs it, forwarding every argument it was given. Usage:
# stable install URL. It selects an immutable OSS release when that mirror is
# available, otherwise it falls back to the matching GitHub Release, then runs
# the real installer while forwarding every argument it was given. Usage:
#
# irm https://penguin.ooo/install.ps1 | iex
# & ([scriptblock]::Create((irm https://penguin.ooo/install.ps1))) -Version v0.2.0
#
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch {
# .NET builds where the enum is immutable already default to TLS 1.2+.
}
# Download fully first, then run: executing a piped stream directly would run a
# truncated download line by line, and the real installer moves the old
# bin/lib/web/node aside before moving the new ones in - a cut connection
# mid-way must never leave a half-executed installer. The installer runs as an
# in-memory script block (not a script file): script files are subject to the
# execution policy, which is Restricted by default on client Windows - while the
# user has already consented to remote code by piping this forwarder into iex.
# Neither this forwarder nor the installer calls `exit`, which in iex/script-block
# context would terminate the user's whole PowerShell session.
$Tmp = Join-Path ([IO.Path]::GetTempPath()) "penguin-install-$PID.ps1"
try {
Invoke-WebRequest -Uri "https://github.com/Prism-Shadow/penguin-harness/releases/latest/download/install.ps1" -OutFile $Tmp -UseBasicParsing
$Installer = [scriptblock]::Create((Get-Content -Path $Tmp -Raw))
& $Installer @args
} finally {
Remove-Item -Force $Tmp -ErrorAction SilentlyContinue
}
& {
$ForwardedArgs = @($args)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$OssOrigin = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com"
$OssReleaseRoot = "$OssOrigin/releases"
$GitHubReleaseRoot = "https://github.com/Prism-Shadow/penguin-harness/releases/download"
$GitHubLatestBase = "https://github.com/Prism-Shadow/penguin-harness/releases/latest/download"
function Fail([string]$Message) {
throw "error: $Message"
}
function Test-HttpsUrl([string]$Value) {
try { $Uri = [Uri]$Value } catch { return $false }
return $Uri.IsAbsoluteUri -and $Uri.Scheme -eq "https"
}
function Test-ReleaseTag([string]$Value) {
return $Value -match '^v[0-9A-Za-z][0-9A-Za-z._-]*$'
}
function Try-DownloadFile([string]$Uri, [string]$OutFile, [int]$TimeoutSec) {
try {
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing -TimeoutSec $TimeoutSec
return $true
} catch {
Remove-Item -LiteralPath $OutFile -Force -ErrorAction SilentlyContinue
return $false
}
}
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch {
# .NET builds where the enum is immutable already default to TLS 1.2+.
}
$SourceMode = if ($env:PENGUIN_DOWNLOAD_SOURCE) { $env:PENGUIN_DOWNLOAD_SOURCE.ToLowerInvariant() } else { "auto" }
if ($SourceMode -notin @("auto", "oss", "github")) {
Fail "PENGUIN_DOWNLOAD_SOURCE must be auto, oss, or github"
}
$RequestedVersion = if ($env:PENGUIN_VERSION) { $env:PENGUIN_VERSION } else { "" }
for ($i = 0; $i -lt $ForwardedArgs.Count; $i++) {
if ([string]$ForwardedArgs[$i] -ieq "-Version" -and $i + 1 -lt $ForwardedArgs.Count) {
$RequestedVersion = [string]$ForwardedArgs[$i + 1]
$i++
}
}
if ($RequestedVersion -and -not (Test-ReleaseTag $RequestedVersion)) {
Fail "invalid release version: $RequestedVersion"
}
# Download fully first, then run: executing a piped stream directly would run a
# truncated download line by line, and the real installer moves the old
# bin/lib/web/node aside before moving the new ones in - a cut connection
# mid-way must never leave a half-executed installer. The installer runs as an
# in-memory script block (not a script file): script files are subject to the
# execution policy, which is Restricted by default on client Windows - while the
# user has already consented to remote code by piping this forwarder into iex.
# Neither this forwarder nor the installer calls `exit`, which in iex/script-block
# context would terminate the user's whole PowerShell session.
$TmpDir = Join-Path ([IO.Path]::GetTempPath()) "penguin-forwarder-$PID"
$InstallerPath = Join-Path $TmpDir "install.ps1"
$ManifestPath = Join-Path $TmpDir "latest.json"
$SelectedBase = ""
$FallbackBase = ""
$OriginalBase = [Environment]::GetEnvironmentVariable("PENGUIN_DOWNLOAD_BASE_URL", "Process")
$OriginalFallback = [Environment]::GetEnvironmentVariable("PENGUIN_DOWNLOAD_FALLBACK_BASE_URL", "Process")
try {
if (Test-Path -LiteralPath $TmpDir) {
Remove-Item -LiteralPath $TmpDir -Recurse -Force
}
New-Item -ItemType Directory -Path $TmpDir | Out-Null
if ($OriginalBase) {
$SelectedBase = $OriginalBase.TrimEnd('/')
$FallbackBase = if ($OriginalFallback) { $OriginalFallback.TrimEnd('/') } else { "" }
if (-not (Test-HttpsUrl $SelectedBase)) { Fail "PENGUIN_DOWNLOAD_BASE_URL must be an absolute HTTPS URL" }
if ($FallbackBase -and -not (Test-HttpsUrl $FallbackBase)) {
Fail "PENGUIN_DOWNLOAD_FALLBACK_BASE_URL must be an absolute HTTPS URL"
}
if (-not (Try-DownloadFile "$SelectedBase/install.ps1" $InstallerPath 30)) {
Fail "could not download the installer from the configured mirror."
}
} elseif ($SourceMode -eq "github") {
$SelectedBase = if ($RequestedVersion) { "$GitHubReleaseRoot/$RequestedVersion" } else { $GitHubLatestBase }
if (-not (Try-DownloadFile "$SelectedBase/install.ps1" $InstallerPath 30)) {
Fail "could not download the installer from GitHub. Check your network, then retry."
}
} else {
$OssTag = $RequestedVersion
$OssBase = if ($OssTag) { "$OssReleaseRoot/$OssTag" } else { "" }
if (-not $OssTag -and (Try-DownloadFile "$OssOrigin/latest.json" $ManifestPath 8)) {
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") {
$OssTag = $CandidateTag
$OssBase = $CandidateBase
}
} catch {
$OssTag = ""
$OssBase = ""
}
}
if ($OssBase -and (Try-DownloadFile "$OssBase/install.ps1" $InstallerPath 30)) {
$SelectedBase = $OssBase
if ($SourceMode -eq "auto") {
$FallbackBase = "$GitHubReleaseRoot/$OssTag"
}
} elseif ($SourceMode -eq "oss") {
Fail "the OSS mirror is unavailable or its release metadata is invalid."
} else {
$SelectedBase = if ($OssTag) { "$GitHubReleaseRoot/$OssTag" } else { $GitHubLatestBase }
if (-not (Try-DownloadFile "$SelectedBase/install.ps1" $InstallerPath 30)) {
Fail "could not download the installer from GitHub. Check your network, then retry."
}
}
}
$env:PENGUIN_DOWNLOAD_BASE_URL = $SelectedBase
if ($FallbackBase) {
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = $FallbackBase
} else {
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
}
$InstallerText = [IO.File]::ReadAllText($InstallerPath, [Text.UTF8Encoding]::new($false))
$Installer = [scriptblock]::Create($InstallerText)
& $Installer @ForwardedArgs
} finally {
if ($null -eq $OriginalBase) {
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_DOWNLOAD_BASE_URL = $OriginalBase
}
if ($null -eq $OriginalFallback) {
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = $OriginalFallback
}
Remove-Item -LiteralPath $TmpDir -Recurse -Force -ErrorAction SilentlyContinue
}
} @args
+121 -4
View File
@@ -2,13 +2,63 @@
# https://penguin.ooo/install.sh - PenguinHarness installer entry point.
#
# GitHub Pages cannot serve HTTP redirects, so this thin forwarder IS the
# stable install URL: it fetches the real installer attached to the latest
# GitHub release and runs it, forwarding every argument it was given. Usage:
# stable install URL. It selects an immutable OSS release when that mirror is
# available, otherwise it falls back to the matching GitHub Release, then runs
# the real installer while forwarding every argument it was given. Usage:
#
# curl -fsSL https://penguin.ooo/install.sh | sh
# curl -fsSL https://penguin.ooo/install.sh | sh -s -- --universal
#
set -eu
OSS_ORIGIN="https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com"
OSS_RELEASE_ROOT="$OSS_ORIGIN/releases"
GITHUB_RELEASE_ROOT="https://github.com/Prism-Shadow/penguin-harness/releases/download"
SOURCE_MODE="${PENGUIN_DOWNLOAD_SOURCE:-auto}"
fail() {
echo "error: $1" >&2
exit 1
}
validate_https_url() {
case "$2" in
https://*) ;;
*) fail "$1 must be an absolute HTTPS URL" ;;
esac
}
is_release_tag() {
case "$1" in
v[0-9A-Za-z]*) ;;
*) return 1 ;;
esac
case "$1" in
*[!0-9A-Za-z._-]*) return 1 ;;
esac
return 0
}
validate_release_tag() {
is_release_tag "$1" || fail "invalid release version: $1"
}
case "$SOURCE_MODE" in
auto | oss | github) ;;
*) fail "PENGUIN_DOWNLOAD_SOURCE must be auto, oss, or github" ;;
esac
REQUESTED_VERSION="${PENGUIN_VERSION:-}"
expect_version=0
for arg in "$@"; do
if [ "$expect_version" -eq 1 ]; then
REQUESTED_VERSION="$arg"
expect_version=0
elif [ "$arg" = "--version" ]; then
expect_version=1
fi
done
[ -z "$REQUESTED_VERSION" ] || validate_release_tag "$REQUESTED_VERSION"
# Download to a file first, then run it: piping straight into `sh` would execute
# a truncated download line by line, and the real installer removes the old
# bin/lib/web/node before moving the new ones in — a cut connection mid-way
@@ -18,7 +68,74 @@ set -eu
# never offer that seam to other local users.
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
curl -fsSL "https://github.com/Prism-Shadow/penguin-harness/releases/latest/download/install.sh" -o "$TMP_DIR/install.sh"
INSTALLER="$TMP_DIR/install.sh"
MANIFEST="$TMP_DIR/latest.json"
SELECTED_BASE=""
FALLBACK_BASE=""
download_installer() {
curl -fsSL --connect-timeout 5 --max-time 30 "$1/install.sh" -o "$INSTALLER"
}
use_github() {
if [ -n "$1" ]; then
SELECTED_BASE="$GITHUB_RELEASE_ROOT/$1"
else
SELECTED_BASE="https://github.com/Prism-Shadow/penguin-harness/releases/latest/download"
fi
FALLBACK_BASE=""
download_installer "$SELECTED_BASE" \
|| fail "could not download the installer from GitHub. Check your network, then retry."
}
EXPLICIT_BASE="${PENGUIN_DOWNLOAD_BASE_URL:-}"
if [ -n "$EXPLICIT_BASE" ]; then
SELECTED_BASE="${EXPLICIT_BASE%/}"
FALLBACK_BASE="${PENGUIN_DOWNLOAD_FALLBACK_BASE_URL:-}"
FALLBACK_BASE="${FALLBACK_BASE%/}"
validate_https_url PENGUIN_DOWNLOAD_BASE_URL "$SELECTED_BASE"
[ -z "$FALLBACK_BASE" ] || validate_https_url PENGUIN_DOWNLOAD_FALLBACK_BASE_URL "$FALLBACK_BASE"
download_installer "$SELECTED_BASE" \
|| fail "could not download the installer from the configured mirror."
elif [ "$SOURCE_MODE" = "github" ]; then
use_github "$REQUESTED_VERSION"
else
OSS_TAG="$REQUESTED_VERSION"
OSS_BASE=""
if [ -n "$OSS_TAG" ]; then
OSS_BASE="$OSS_RELEASE_ROOT/$OSS_TAG"
elif curl -fsSL --connect-timeout 3 --max-time 8 "$OSS_ORIGIN/latest.json" -o "$MANIFEST" 2>/dev/null; then
schema_version="$(sed -n 's/.*"schemaVersion":[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$MANIFEST" | head -n 1)"
candidate_tag="$(sed -n 's/.*"tag":[[:space:]]*"\([^"]*\)".*/\1/p' "$MANIFEST" | head -n 1)"
candidate_base="$(sed -n 's/.*"releaseBaseUrl":[[:space:]]*"\([^"]*\)".*/\1/p' "$MANIFEST" | head -n 1)"
if [ "$schema_version" = "1" ] && is_release_tag "$candidate_tag"; then
if [ "$candidate_base" = "$OSS_RELEASE_ROOT/$candidate_tag" ]; then
OSS_TAG="$candidate_tag"
OSS_BASE="$candidate_base"
fi
fi
fi
if [ -n "$OSS_BASE" ] && download_installer "$OSS_BASE" 2>/dev/null; then
SELECTED_BASE="$OSS_BASE"
if [ "$SOURCE_MODE" = "auto" ]; then
FALLBACK_BASE="$GITHUB_RELEASE_ROOT/$OSS_TAG"
fi
elif [ "$SOURCE_MODE" = "oss" ]; then
fail "the OSS mirror is unavailable or its release metadata is invalid."
else
use_github "$OSS_TAG"
fi
fi
rc=0
sh "$TMP_DIR/install.sh" "$@" || rc=$?
(
export PENGUIN_DOWNLOAD_BASE_URL="$SELECTED_BASE"
if [ -n "$FALLBACK_BASE" ]; then
export PENGUIN_DOWNLOAD_FALLBACK_BASE_URL="$FALLBACK_BASE"
else
unset PENGUIN_DOWNLOAD_FALLBACK_BASE_URL
fi
sh "$INSTALLER" "$@"
) || rc=$?
exit "$rc"
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# Install a checksum-pinned ossutil 2 binary into a caller-provided directory.
# Usage: install-ossutil.sh <bin-dir>
set -eu
BIN_DIR="${1:?usage: install-ossutil.sh <bin-dir>}"
VERSION="2.3.0"
ARCHIVE="ossutil-$VERSION-linux-amd64.zip"
ARCHIVE_SHA256="3ae4d9fc85a7a6e9f5654d1599766f1a3a42a3692870887b5ae9338d582ef65a"
DOWNLOAD_URL="https://gosspublic.alicdn.com/ossutil/v2/$VERSION/$ARCHIVE"
command -v curl >/dev/null 2>&1 || {
echo "error: curl is required" >&2
exit 1
}
command -v sha256sum >/dev/null 2>&1 || {
echo "error: sha256sum is required" >&2
exit 1
}
command -v unzip >/dev/null 2>&1 || {
echo "error: unzip is required" >&2
exit 1
}
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
curl --proto '=https' --tlsv1.2 -fsSL "$DOWNLOAD_URL" -o "$WORK_DIR/$ARCHIVE"
printf '%s %s\n' "$ARCHIVE_SHA256" "$WORK_DIR/$ARCHIVE" | sha256sum -c -
unzip -q "$WORK_DIR/$ARCHIVE" -d "$WORK_DIR/extracted"
OSSUTIL_SOURCE="$(find "$WORK_DIR/extracted" -type f -name ossutil -print -quit)"
[ -n "$OSSUTIL_SOURCE" ] || {
echo "error: ossutil binary not found in $ARCHIVE" >&2
exit 1
}
mkdir -p "$BIN_DIR"
install -m 0755 "$OSSUTIL_SOURCE" "$BIN_DIR/ossutil"
"$BIN_DIR/ossutil" version
+220
View File
@@ -0,0 +1,220 @@
#!/bin/sh
# Mirror the exact assets downloaded from a GitHub Release into Alibaba Cloud OSS.
#
# Usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]
# update-latest: true only when <tag> is GitHub's current latest Release.
#
# Required environment:
# OSS_BUCKET, OSS_REGION, OSS_ENDPOINT, OSS_PUBLIC_BASE_URL and temporary
# OSS_* credentials.
set -eu
RELEASE_DIR="${1:?usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]}"
TAG="${2:?usage: publish-release-to-oss.sh <release-dir> <tag> [update-latest]}"
UPDATE_LATEST="${3:-false}"
OSSUTIL_BIN="${OSSUTIL_BIN:-ossutil}"
require_env() {
eval "value=\${$1:-}"
[ -n "$value" ] || {
echo "error: required environment variable $1 is empty" >&2
exit 1
}
}
for name in OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
require_env "$name"
done
[ -d "$RELEASE_DIR" ] || {
echo "error: release directory not found: $RELEASE_DIR" >&2
exit 1
}
case "$TAG" in
v[0-9]*) VERSION="${TAG#v}" ;;
*)
echo "error: release tag must start with v followed by a digit: $TAG" >&2
exit 1
;;
esac
case "$TAG" in
*[!A-Za-z0-9._+-]*|*..*)
echo "error: release tag is not safe for an OSS object prefix: $TAG" >&2
exit 1
;;
esac
case "$UPDATE_LATEST" in
true|false) ;;
*)
echo "error: update-latest must be true or false" >&2
exit 1
;;
esac
command -v "$OSSUTIL_BIN" >/dev/null 2>&1 || {
echo "error: ossutil not found: $OSSUTIL_BIN" >&2
exit 1
}
command -v sha256sum >/dev/null 2>&1 || {
echo "error: sha256sum is required" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "error: jq is required" >&2
exit 1
}
BUNDLES="
penguin-linux-x64.tar.gz
penguin-linux-arm64.tar.gz
penguin-darwin-x64.tar.gz
penguin-darwin-arm64.tar.gz
penguin-universal.tar.gz
penguin-win32-x64.zip
"
FILES="$BUNDLES
penguin-linux-x64.tar.gz.sha256
penguin-linux-arm64.tar.gz.sha256
penguin-darwin-x64.tar.gz.sha256
penguin-darwin-arm64.tar.gz.sha256
penguin-universal.tar.gz.sha256
penguin-win32-x64.zip.sha256
SHA256SUMS
install.sh
install.ps1
"
for file in $FILES; do
[ -f "$RELEASE_DIR/$file" ] || {
echo "error: missing GitHub Release asset: $file" >&2
exit 1
}
done
for bundle in $BUNDLES; do
(cd "$RELEASE_DIR" && sha256sum -c "$bundle.sha256")
done
(cd "$RELEASE_DIR" && sha256sum -c SHA256SUMS)
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
oss_cp() {
if [ -n "$3" ]; then
"$OSSUTIL_BIN" cp "$1" "$2" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--force \
--no-progress \
--cache-control "$3"
else
"$OSSUTIL_BIN" cp "$1" "$2" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--force \
--no-progress
fi
}
oss_put_if_absent() {
local_file="$1"
object_key="$2"
cache_control="$3"
"$OSSUTIL_BIN" api put-object \
--bucket "$OSS_BUCKET" \
--key "$object_key" \
--body "file://$local_file" \
--forbid-overwrite \
--cache-control "$cache_control" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION"
}
file_sha256() {
sha256sum "$1" | awk '{print $1}'
}
verify_remote_file() {
local_file="$1"
remote_uri="$2"
remote_file="$WORK_DIR/remote-$(basename "$local_file")"
rm -f "$remote_file"
oss_cp "$remote_uri" "$remote_file" ""
local_hash="$(file_sha256 "$local_file")"
remote_hash="$(file_sha256 "$remote_file")"
[ "$local_hash" = "$remote_hash" ] || {
echo "error: OSS object differs from the GitHub Release asset: $remote_uri" >&2
exit 1
}
}
upload_immutable_file() {
local_file="$1"
object_key="$2"
remote_uri="oss://$OSS_BUCKET/$object_key"
existing_file="$WORK_DIR/existing-$(basename "$local_file")"
rm -f "$existing_file"
# An exact-key download avoids needing ListObjects. Existing identical bytes make retries
# idempotent; different bytes fail before any upload is attempted.
if oss_cp "$remote_uri" "$existing_file" "" >/dev/null 2>&1; then
if [ "$(file_sha256 "$local_file")" = "$(file_sha256 "$existing_file")" ]; then
echo "Already mirrored: $remote_uri"
return
fi
echo "error: immutable OSS object already exists with different content: $remote_uri" >&2
exit 1
fi
echo "Uploading: $remote_uri"
if ! oss_put_if_absent "$local_file" "$object_key" "public,max-age=31536000,immutable"; then
# A concurrent retry may have won the create race. It is safe only if the resulting bytes match.
echo "Upload did not create $remote_uri; checking whether an identical object now exists."
fi
verify_remote_file "$local_file" "$remote_uri"
}
RELEASE_PREFIX="releases/$TAG"
for file in $FILES; do
upload_immutable_file "$RELEASE_DIR/$file" "$RELEASE_PREFIX/$file"
done
if [ "$UPDATE_LATEST" = "true" ]; then
# Re-check at the last possible moment. Another Release can finish while this job is
# transferring large assets; an older retry must never roll latest.json backwards.
require_env GH_TOKEN
require_env GITHUB_REPOSITORY
command -v gh >/dev/null 2>&1 || {
echo "error: gh is required when updating latest.json" >&2
exit 1
}
CURRENT_LATEST_TAG="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)"
if [ "$TAG" != "$CURRENT_LATEST_TAG" ]; then
echo "Skipping latest.json because GitHub's latest Release changed to $CURRENT_LATEST_TAG."
UPDATE_LATEST=false
fi
fi
if [ "$UPDATE_LATEST" = "true" ]; then
PUBLIC_BASE="${OSS_PUBLIC_BASE_URL%/}/$RELEASE_PREFIX"
jq -n \
--arg tag "$TAG" \
--arg version "$VERSION" \
--arg releaseBaseUrl "$PUBLIC_BASE" \
'{
schemaVersion: 1,
tag: $tag,
version: $version,
releaseBaseUrl: $releaseBaseUrl
}' > "$WORK_DIR/latest.json"
LATEST_URI="oss://$OSS_BUCKET/latest.json"
echo "Updating latest release pointer: $LATEST_URI"
oss_cp "$WORK_DIR/latest.json" "$LATEST_URI" "no-cache"
verify_remote_file "$WORK_DIR/latest.json" "$LATEST_URI"
else
echo "Skipping latest.json because update-latest is false."
fi
echo "OSS mirror verified for $TAG."
+154 -3
View File
@@ -11,12 +11,19 @@ $Installer = Join-Path $RepoRoot "install.ps1"
$WorkDir = Join-Path ([IO.Path]::GetTempPath()) "penguin-installer-tests-$PID"
$OriginalPath = $env:Path
$OriginalOs = $env:OS
$OriginalDownloadBaseUrl = $env:PENGUIN_DOWNLOAD_BASE_URL
$OriginalDownloadFallbackBaseUrl = $env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL
$OriginalDownloadSource = $env:PENGUIN_DOWNLOAD_SOURCE
$OriginalArchive = $env:PENGUIN_ARCHIVE
$OriginalInstallDir = $env:PENGUIN_INSTALL_DIR
$OriginalVersion = $env:PENGUIN_VERSION
$Fixture = @{
Requests = [Collections.Generic.List[string]]::new()
Mode = "canonical"
GoodBundle = $null
BadInnerBundle = $null
LegacyArchive = $null
Installer = $Installer
}
$global:PenguinInstallerFixture = $Fixture
@@ -53,13 +60,39 @@ function global:Invoke-WebRequest {
param(
[Parameter(Mandatory = $true)][string]$Uri,
[Parameter(Mandatory = $true)][string]$OutFile,
[switch]$UseBasicParsing
[switch]$UseBasicParsing,
[int]$TimeoutSec = 0
)
$f = $global:PenguinInstallerFixture
$f.Requests.Add($Uri)
if ($f.Mode -eq "404") { throw "fixture 404: $Uri" }
if ($f.Mode -eq "network") { throw "fixture network failure: $Uri" }
if ($f.Mode -eq "primary-network" -and $Uri -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*") {
throw "fixture primary network failure"
}
if ($f.Mode -eq "forced-oss-payload" -and
$Uri -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*/penguin-*") {
throw "fixture forced OSS payload failure"
}
if ($f.Mode -eq "forwarder-auto-github" -and $Uri -like "*/latest.json") {
throw "fixture OSS metadata failure"
}
switch -Wildcard ($Uri) {
"*/latest.json" {
if ($f.Mode -eq "forwarder-invalid-metadata") {
'{"schemaVersion":1,"tag":"../invalid","releaseBaseUrl":"https://example.invalid"}' |
Set-Content -LiteralPath $OutFile -Encoding ascii
} else {
@{
schemaVersion = 1
tag = "v0.0.0-test"
releaseBaseUrl = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"
} | ConvertTo-Json | Set-Content -LiteralPath $OutFile -Encoding ascii
}
}
"*/install.ps1" {
Copy-Item -LiteralPath $f.Installer -Destination $OutFile
}
"*/penguin-win32-x64.zip.sha256" {
switch ($f.Mode) {
"outer-sha-mismatch" {
@@ -94,17 +127,54 @@ function Invoke-OnlineCase(
$Arguments = @{ InstallDir = $InstallDir }
if ($Version) { $Arguments.Version = $Version }
$Succeeded = $true
try { & $Installer @Arguments *>&1 | Out-Null } catch { $Succeeded = $false }
$Output = @()
try { $Output = @(& $Installer @Arguments *>&1) } catch { $Succeeded = $false }
Assert-True ($Succeeded -eq $ShouldSucceed) "$Name returned an unexpected result"
Assert-True ($Fixture.Requests.Count -eq $ExpectedRequests) `
"$Name made $($Fixture.Requests.Count) requests, expected $ExpectedRequests"
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests) }
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests); Output = @($Output) }
}
function Invoke-ForwarderCase(
[string]$Name,
[string]$Mode,
[string]$Source,
[int]$ExpectedRequests,
[string]$Version = "",
[bool]$ShouldSucceed = $true
) {
$Fixture.Mode = $Mode
$Fixture.Requests.Clear()
$InstallDir = Join-Path $WorkDir "$Name-install"
if ($Version) {
Remove-Item Env:\PENGUIN_ARCHIVE -ErrorAction SilentlyContinue
$env:PENGUIN_VERSION = $Version
} else {
$env:PENGUIN_ARCHIVE = $Fixture.GoodBundle
Remove-Item Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
}
$env:PENGUIN_INSTALL_DIR = $InstallDir
$env:PENGUIN_DOWNLOAD_SOURCE = $Source
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL, Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
$Forwarder = Join-Path $RepoRoot "packages\landing\public\install.ps1"
$Output = @()
$Succeeded = $true
try { $Output = @(& $Forwarder *>&1) } catch { $Succeeded = $false }
Assert-True ($Succeeded -eq $ShouldSucceed) "$Name returned an unexpected result"
Assert-True ($Fixture.Requests.Count -eq $ExpectedRequests) `
"$Name made $($Fixture.Requests.Count) requests, expected $ExpectedRequests"
Assert-True (-not (($Output | Out-String) -match 'aliyuncs\.com')) `
"$Name exposed the OSS URL in normal output"
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests); Output = @($Output) }
}
try {
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
# Keep the fixture tests away from the runner's user registry Path.
$env:OS = "PenguinInstallerFixtureTest"
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL, Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL, `
Env:\PENGUIN_DOWNLOAD_SOURCE, Env:\PENGUIN_ARCHIVE, Env:\PENGUIN_INSTALL_DIR, `
Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
# --- Offline program archive: good install, then a failing upgrade must roll back. ---
$InstallDir = Join-Path $WorkDir "offline-installed"
@@ -171,6 +241,57 @@ try {
"canonical did not request the canonical bundle"
$Version = & (Join-Path $canonical.InstallDir "bin\penguin.cmd") --version
Assert-True ($Version -eq "fixture-old") "canonical bundle was not installed"
$env:PENGUIN_DOWNLOAD_BASE_URL = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"
$override = Invoke-OnlineCase "download-base-override" "canonical" "" $true 2
Assert-True ($override.Requests[0] -eq "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/penguin-win32-x64.zip") `
"download base override did not request the configured asset directory"
Assert-True (($override.Output | Out-String) -match 'OSS mirror') `
"download base override did not identify the OSS mirror"
Assert-True (-not (($override.Output | Out-String) -match 'aliyuncs\.com')) `
"download base override exposed the OSS URL in normal output"
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = "https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.0.0-test"
$fallback = Invoke-OnlineCase "download-fallback" "primary-network" "" $true 3
Assert-True ($fallback.Requests[0] -like "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*") `
"download fallback did not try the primary source first"
Assert-True ($fallback.Requests[1] -like "https://github.com/*/penguin-win32-x64.zip") `
"download fallback did not use the same-version GitHub source"
Assert-True (-not (($fallback.Output | Out-String) -match 'aliyuncs\.com')) `
"download fallback exposed the OSS URL in normal output"
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL
$forwarderOss = Invoke-ForwarderCase "forwarder-oss" "forwarder-oss" "auto" 2
Assert-True ($forwarderOss.Requests[0] -like "*/latest.json") `
"OSS forwarder did not request release metadata first"
Assert-True ($forwarderOss.Requests[1] -like "*/releases/v0.0.0-test/install.ps1") `
"OSS forwarder did not request the versioned installer"
$forwarderGitHub = Invoke-ForwarderCase "forwarder-auto-github" "forwarder-auto-github" "auto" 2
Assert-True ($forwarderGitHub.Requests[1] -like "https://github.com/*/releases/latest/download/install.ps1") `
"forwarder did not fall back to the GitHub installer"
$invalidMetadata = Invoke-ForwarderCase "forwarder-invalid-metadata" "forwarder-invalid-metadata" "auto" 2
Assert-True ($invalidMetadata.Requests[1] -like "https://github.com/*/releases/latest/download/install.ps1") `
"invalid OSS metadata did not fall back to the GitHub installer"
$forcedGitHub = Invoke-ForwarderCase "forwarder-github" "canonical" "github" 1
Assert-True ($forcedGitHub.Requests[0] -like "https://github.com/*/releases/latest/download/install.ps1") `
"forced GitHub mode did not request the GitHub installer"
$forcedOss = Invoke-ForwarderCase "forwarder-forced-oss-no-fallback" `
"forced-oss-payload" "oss" 2 "v0.0.0-test" $false
Assert-True (-not (($forcedOss.Requests | Out-String) -match 'github\.com')) `
"forced OSS mode unexpectedly fell back to GitHub"
$pinnedForwarder = Invoke-ForwarderCase "forwarder-pinned" "canonical" "auto" 3 "v0.0.0-test"
Assert-True ($pinnedForwarder.Requests[0] -like "*/releases/v0.0.0-test/install.ps1") `
"pinned forwarder did not request the versioned installer"
Assert-True ($pinnedForwarder.Requests[1] -like "*/releases/v0.0.0-test/penguin-win32-x64.zip") `
"pinned installer did not keep the selected release version"
Remove-Item Env:\PENGUIN_ARCHIVE, Env:\PENGUIN_INSTALL_DIR, Env:\PENGUIN_DOWNLOAD_SOURCE, Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
Invoke-OnlineCase "outer-mismatch" "outer-sha-mismatch" "" $false 2 | Out-Null
Invoke-OnlineCase "inner-mismatch" "inner-sha-mismatch" "" $false 2 | Out-Null
Invoke-OnlineCase "latest-404" "404" "" $false 1 | Out-Null
@@ -183,6 +304,36 @@ try {
} finally {
$env:Path = $OriginalPath
$env:OS = $OriginalOs
if ($null -eq $OriginalDownloadBaseUrl) {
Remove-Item Env:\PENGUIN_DOWNLOAD_BASE_URL -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_DOWNLOAD_BASE_URL = $OriginalDownloadBaseUrl
}
if ($null -eq $OriginalDownloadFallbackBaseUrl) {
Remove-Item Env:\PENGUIN_DOWNLOAD_FALLBACK_BASE_URL -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_DOWNLOAD_FALLBACK_BASE_URL = $OriginalDownloadFallbackBaseUrl
}
if ($null -eq $OriginalDownloadSource) {
Remove-Item Env:\PENGUIN_DOWNLOAD_SOURCE -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_DOWNLOAD_SOURCE = $OriginalDownloadSource
}
if ($null -eq $OriginalArchive) {
Remove-Item Env:\PENGUIN_ARCHIVE -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_ARCHIVE = $OriginalArchive
}
if ($null -eq $OriginalInstallDir) {
Remove-Item Env:\PENGUIN_INSTALL_DIR -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_INSTALL_DIR = $OriginalInstallDir
}
if ($null -eq $OriginalVersion) {
Remove-Item Env:\PENGUIN_VERSION -ErrorAction SilentlyContinue
} else {
$env:PENGUIN_VERSION = $OriginalVersion
}
Remove-Item Function:\Invoke-WebRequest -ErrorAction SilentlyContinue
Remove-Variable PenguinInstallerFixture -Scope Global -ErrorAction SilentlyContinue
if (Test-Path -LiteralPath $WorkDir) { Remove-Item -LiteralPath $WorkDir -Recurse -Force }
+109 -2
View File
@@ -242,19 +242,36 @@ url=""
while [ $# -gt 0 ]; do
case "$1" in
-o) output="$2"; shift 2 ;;
--connect-timeout | --max-time) shift 2 ;;
-*) shift ;;
*) url="$1"; shift ;;
esac
done
printf '%s\n' "$url" >> "$REQUEST_LOG"
base="${url##*/}"
case "$MODE:$url" in
primary-network:https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*) exit 7 ;;
forced-oss-payload:https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/*/penguin-*) exit 7 ;;
esac
case "$MODE:$base" in
forwarder-auto-github:latest.json) exit 7 ;;
forwarder-invalid-metadata:latest.json)
printf '%s\n' '{"schemaVersion":1,"tag":"../invalid","releaseBaseUrl":"https://example.invalid"}' > "$output"
;;
forwarder-oss:latest.json | forced-oss-payload:latest.json)
printf '%s\n' '{"schemaVersion":1,"tag":"v0.0.0-test","releaseBaseUrl":"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test"}' > "$output"
;;
forwarder-oss:install.sh | forced-oss-payload:install.sh | forwarder-auto-github:install.sh | forwarder-invalid-metadata:install.sh | canonical:install.sh) cp "$ROOT_DIR/install.sh" "$output" ;;
404:penguin-*) exit 22 ;;
network:penguin-*) exit 7 ;;
outer-sha-mismatch:penguin-*.sha256) printf '%064d %s\n' 0 "${base%.sha256}" > "$output" ;;
outer-sha-mismatch:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
inner-sha-mismatch:penguin-*.sha256) cp "$BAD_BUNDLE.sha256" "$output" ;;
inner-sha-mismatch:penguin-*) cp "$BAD_BUNDLE" "$output" ;;
primary-network:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
primary-network:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
forced-oss-payload:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
forced-oss-payload:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
legacy:penguin-*.sha256) cp "$LEGACY_ARCHIVE.sha256" "$output" ;;
legacy:penguin-*) cp "$LEGACY_ARCHIVE" "$output" ;;
canonical:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
@@ -263,7 +280,7 @@ case "$MODE:$base" in
esac
EOF
chmod +x "$STUB_BIN/curl"
export ARTIFACT_DIR BAD_BUNDLE LEGACY_ARCHIVE
export ARTIFACT_DIR BAD_BUNDLE LEGACY_ARCHIVE ROOT_DIR
run_online_case() {
name="$1"
@@ -271,13 +288,18 @@ run_online_case() {
version="$3"
expected="$4"
expected_requests="$5"
download_base_url="${6:-}"
download_fallback_base_url="${7:-}"
CASE_LOG="$WORK_DIR/$name.log"
CASE_OUTPUT="$WORK_DIR/$name.output"
CASE_INSTALL="$WORK_DIR/$name-install"
: > "$CASE_LOG"
set +e
REQUEST_LOG="$CASE_LOG" MODE="$mode" PATH="$STUB_BIN:$PATH" \
HOME="$WORK_DIR/$name-home" PENGUIN_INSTALL_DIR="$CASE_INSTALL" \
PENGUIN_VERSION="$version" sh "$ROOT_DIR/install.sh" >/dev/null 2>&1
PENGUIN_VERSION="$version" PENGUIN_DOWNLOAD_BASE_URL="$download_base_url" \
PENGUIN_DOWNLOAD_FALLBACK_BASE_URL="$download_fallback_base_url" \
sh "$ROOT_DIR/install.sh" >"$CASE_OUTPUT" 2>&1
status=$?
set -e
if [ "$expected" = "success" ]; then
@@ -294,6 +316,22 @@ run_online_case canonical canonical "" success 2
|| fail_test "canonical online install did not produce a working command"
grep -q "/releases/latest/download/$HOST_ASSET\$" "$WORK_DIR/canonical.log" \
|| fail_test "canonical did not request the canonical bundle"
run_online_case download-base-override canonical "" success 2 \
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test" ""
grep -q "OSS mirror" "$WORK_DIR/download-base-override.output" \
|| fail_test "download base override did not identify the OSS mirror"
! grep -q "aliyuncs.com" "$WORK_DIR/download-base-override.output" \
|| fail_test "download base override exposed the OSS URL in normal output"
run_online_case download-fallback primary-network "" success 3 \
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test" \
"https://github.com/Prism-Shadow/penguin-harness/releases/download/v0.0.0-test"
[ "$(sed -n '1p' "$WORK_DIR/download-fallback.log")" = \
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/$HOST_ASSET" ] \
|| fail_test "download fallback did not try the primary source first"
grep -q "github.com/.*/releases/download/v0.0.0-test/$HOST_ASSET\$" "$WORK_DIR/download-fallback.log" \
|| fail_test "download fallback did not use the same-version GitHub source"
! grep -q "aliyuncs.com" "$WORK_DIR/download-fallback.output" \
|| fail_test "download fallback exposed the OSS URL in normal output"
run_online_case outer-mismatch outer-sha-mismatch "" failure 2
run_online_case inner-mismatch inner-sha-mismatch "" failure 2
run_online_case latest-404 404 "" failure 1
@@ -302,4 +340,73 @@ run_online_case pinned-legacy legacy v0.1.4 success 2
grep -q "/releases/download/v0.1.4/$HOST_ASSET\$" "$WORK_DIR/pinned-legacy.log" \
|| fail_test "pinned legacy did not request the pinned asset"
# --- Stable penguin.ooo forwarder: prefer a validated immutable OSS release, but fall back to
# GitHub when the metadata probe fails. The real installer uses a local fixture here so the
# test isolates bootstrap routing from bundle download behavior above. ---
run_forwarder_case() {
name="$1"
mode="$2"
expected_requests="$3"
source="${4:-auto}"
version="${5:-}"
expected="${6:-success}"
CASE_LOG="$WORK_DIR/$name.log"
CASE_OUTPUT="$WORK_DIR/$name.output"
CASE_INSTALL="$WORK_DIR/$name-install"
: > "$CASE_LOG"
if [ -n "$version" ]; then
archive=""
else
archive="$ARTIFACT_DIR/$HOST_ASSET"
fi
set +e
REQUEST_LOG="$CASE_LOG" MODE="$mode" PATH="$STUB_BIN:$PATH" \
HOME="$WORK_DIR/$name-home" PENGUIN_INSTALL_DIR="$CASE_INSTALL" \
PENGUIN_ARCHIVE="$archive" PENGUIN_VERSION="$version" \
PENGUIN_DOWNLOAD_SOURCE="$source" PENGUIN_DOWNLOAD_BASE_URL="" \
PENGUIN_DOWNLOAD_FALLBACK_BASE_URL="" \
sh "$ROOT_DIR/packages/landing/public/install.sh" >"$CASE_OUTPUT" 2>&1
status=$?
set -e
if [ "$expected" = "success" ]; then
[ "$status" -eq 0 ] || fail_test "$name unexpectedly failed"
else
[ "$status" -ne 0 ] || fail_test "$name unexpectedly succeeded"
fi
[ "$(wc -l < "$CASE_LOG" | tr -d ' ')" -eq "$expected_requests" ] \
|| fail_test "$name made an unexpected number of requests"
! grep -q "aliyuncs.com" "$CASE_OUTPUT" \
|| fail_test "$name exposed the OSS URL in normal output"
}
run_forwarder_case forwarder-oss forwarder-oss 2
grep -q "/latest.json\$" "$WORK_DIR/forwarder-oss.log" \
|| fail_test "OSS forwarder did not request release metadata first"
grep -q "/releases/v0.0.0-test/install.sh\$" "$WORK_DIR/forwarder-oss.log" \
|| fail_test "OSS forwarder did not request the versioned installer"
run_forwarder_case forwarder-auto-github forwarder-auto-github 2
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-auto-github.log" \
|| fail_test "forwarder did not fall back to the GitHub installer"
run_forwarder_case forwarder-invalid-metadata forwarder-invalid-metadata 2
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-invalid-metadata.log" \
|| fail_test "invalid OSS metadata did not fall back to the GitHub installer"
run_forwarder_case forwarder-github canonical 1 github
grep -q "github.com/.*/releases/latest/download/install.sh\$" "$WORK_DIR/forwarder-github.log" \
|| fail_test "forced GitHub mode did not request the GitHub installer"
run_forwarder_case forwarder-forced-oss-no-fallback forced-oss-payload 2 oss v0.0.0-test failure
! grep -q "github.com" "$WORK_DIR/forwarder-forced-oss-no-fallback.log" \
|| fail_test "forced OSS mode unexpectedly fell back to GitHub"
run_forwarder_case forwarder-pinned canonical 3 auto v0.0.0-test
[ "$(sed -n '1p' "$WORK_DIR/forwarder-pinned.log")" = \
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/install.sh" ] \
|| fail_test "pinned forwarder did not request the versioned installer"
[ "$(sed -n '2p' "$WORK_DIR/forwarder-pinned.log")" = \
"https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com/releases/v0.0.0-test/$HOST_ASSET" ] \
|| fail_test "pinned installer did not keep the selected release version"
echo "Installer bundle, offline, rollback and online tests passed."
+102
View File
@@ -0,0 +1,102 @@
#!/bin/sh
# Verify a GitHub Actions OIDC staging role can round-trip an object under staging/
# and cannot write either releases/ or the production latest.json pointer.
set -eu
OSSUTIL_BIN="${OSSUTIL_BIN:-ossutil}"
RUN_ID="${GITHUB_RUN_ID:-manual}"
RUN_ATTEMPT="${GITHUB_RUN_ATTEMPT:-1}"
PREFIX="${1:-staging/$RUN_ID-$RUN_ATTEMPT}"
require_env() {
eval "value=\${$1:-}"
[ -n "$value" ] || {
echo "error: required environment variable $1 is empty" >&2
exit 1
}
}
for name in OSS_BUCKET OSS_REGION OSS_ENDPOINT OSS_PUBLIC_BASE_URL; do
require_env "$name"
done
case "$PREFIX" in
staging/*) ;;
*)
echo "error: staging prefix must start with staging/: $PREFIX" >&2
exit 1
;;
esac
command -v "$OSSUTIL_BIN" >/dev/null 2>&1 || {
echo "error: ossutil not found: $OSSUTIL_BIN" >&2
exit 1
}
command -v curl >/dev/null 2>&1 || {
echo "error: curl is required" >&2
exit 1
}
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
PROBE="$WORK_DIR/oidc-probe.txt"
DOWNLOADED="$WORK_DIR/downloaded.txt"
printf 'repository=%s\nrun_id=%s\nrun_attempt=%s\ncommit=%s\n' \
"${GITHUB_REPOSITORY:-unknown}" "$RUN_ID" "$RUN_ATTEMPT" "${GITHUB_SHA:-unknown}" > "$PROBE"
oss_cp() {
"$OSSUTIL_BIN" cp "$1" "$2" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--force \
--no-progress
}
STAGING_URI="oss://$OSS_BUCKET/$PREFIX/oidc-probe.txt"
oss_cp "$PROBE" "$STAGING_URI"
oss_cp "$STAGING_URI" "$DOWNLOADED"
cmp "$PROBE" "$DOWNLOADED"
echo "Staging upload/download verified: $STAGING_URI"
DENIED_URI="oss://$OSS_BUCKET/releases/_staging-deny-probe/$RUN_ID-$RUN_ATTEMPT.txt"
if oss_cp "$PROBE" "$DENIED_URI" >"$WORK_DIR/denied.log" 2>&1; then
echo "error: staging role unexpectedly wrote to production: $DENIED_URI" >&2
echo "Remove that probe manually and fix the RAM policy before continuing." >&2
exit 1
fi
if ! grep -Eiq 'AccessDenied|Forbidden|(^|[^0-9])403([^0-9]|$)' "$WORK_DIR/denied.log"; then
echo "error: production probe failed, but not with a recognizable access-denied response" >&2
cat "$WORK_DIR/denied.log" >&2
exit 1
fi
echo "Production write correctly denied for the staging role."
# Probe the exact latest.json permission without risking an overwrite. The existing public
# object is used as the body and x-oss-forbid-overwrite makes the request non-destructive:
# AccessDenied is expected; FileAlreadyExists means the role was incorrectly authorized.
LATEST_COPY="$WORK_DIR/latest.json"
LATEST_URL="${OSS_PUBLIC_BASE_URL%/}/latest.json"
curl --proto '=https' --tlsv1.2 -fsSL "$LATEST_URL" -o "$LATEST_COPY"
[ -s "$LATEST_COPY" ] || {
echo "error: downloaded latest.json is empty: $LATEST_URL" >&2
exit 1
}
if "$OSSUTIL_BIN" api put-object \
--bucket "$OSS_BUCKET" \
--key latest.json \
--body "file://$LATEST_COPY" \
--forbid-overwrite \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" >"$WORK_DIR/latest-denied.log" 2>&1; then
echo "error: staging role unexpectedly wrote the production latest.json pointer" >&2
exit 1
fi
if grep -Eiq 'AccessDenied|Forbidden|(^|[^0-9])403([^0-9]|$)' "$WORK_DIR/latest-denied.log"; then
echo "Production latest.json write correctly denied for the staging role."
elif grep -Eiq 'FileAlreadyExists|(^|[^0-9])409([^0-9]|$)' "$WORK_DIR/latest-denied.log"; then
echo "error: staging role is authorized to write latest.json; overwrite was blocked by OSS" >&2
exit 1
else
echo "error: latest.json probe failed, but not with a recognizable access-denied response" >&2
cat "$WORK_DIR/latest-denied.log" >&2
exit 1
fi