feat(release): add Alibaba Cloud OSS distribution (#166)
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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 校验 |
|
||||
| 升级 | 重新执行安装脚本即可,文件原子替换 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user