mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +08:00
Document: github release for RAGFlow Go CLI (#16036)
### What problem does this PR solve? As title ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
This commit is contained in:
91
.github/workflows/release.yml
vendored
91
.github/workflows/release.yml
vendored
@@ -25,6 +25,7 @@ concurrency:
|
||||
jobs:
|
||||
release:
|
||||
runs-on: [ "self-hosted", "ragflow-release" ]
|
||||
|
||||
steps:
|
||||
- name: Ensure workspace ownership
|
||||
run: echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE}
|
||||
@@ -37,6 +38,13 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
# https://github.com/actions/setup-go
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Prepare release body
|
||||
run: |
|
||||
if [[ ${GITHUB_EVENT_NAME} != "schedule" ]]; then
|
||||
@@ -52,8 +60,10 @@ jobs:
|
||||
PRERELEASE=true
|
||||
echo "Workflow triggered by schedule"
|
||||
fi
|
||||
|
||||
echo "RELEASE_TAG=${RELEASE_TAG}" >> ${GITHUB_ENV}
|
||||
echo "PRERELEASE=${PRERELEASE}" >> ${GITHUB_ENV}
|
||||
|
||||
RELEASE_DATETIME=$(date --rfc-3339=seconds)
|
||||
echo Release ${RELEASE_TAG} created from ${GITHUB_SHA} at ${RELEASE_DATETIME} > release_body.md
|
||||
|
||||
@@ -73,15 +83,88 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Create or overwrite a release
|
||||
# https://github.com/actions/upload-release-asset has been replaced by https://github.com/softprops/action-gh-release
|
||||
- name: Build Go CLI release binaries
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
CLI_NAME="ragflow_cli"
|
||||
|
||||
CLI_MAIN="./cmd/ragflow_cli.go"
|
||||
|
||||
DIST_DIR="dist/cli"
|
||||
|
||||
mkdir -p "${DIST_DIR}"
|
||||
|
||||
if [[ ! -e "${CLI_MAIN}" ]]; then
|
||||
echo "::error::Go CLI entry does not exist: ${CLI_MAIN}"
|
||||
echo "::error::Please update CLI_MAIN in .github/workflows/release.yml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building Go CLI release binaries"
|
||||
echo "CLI name: ${CLI_NAME}"
|
||||
echo "CLI main: ${CLI_MAIN}"
|
||||
echo "Release tag: ${RELEASE_TAG}"
|
||||
echo "Commit: ${GITHUB_SHA}"
|
||||
|
||||
build_one() {
|
||||
local goos="$1"
|
||||
local goarch="$2"
|
||||
local ext="$3"
|
||||
|
||||
local output="${DIST_DIR}/${CLI_NAME}-${RELEASE_TAG}-${goos}-${goarch}${ext}"
|
||||
|
||||
echo "Building ${goos}/${goarch} -> ${output}"
|
||||
|
||||
CGO_ENABLED=0 \
|
||||
GOOS="${goos}" \
|
||||
GOARCH="${goarch}" \
|
||||
go build \
|
||||
-trimpath \
|
||||
-ldflags="-s -w -X main.version=${RELEASE_TAG} -X main.commit=${GITHUB_SHA}" \
|
||||
-o "${output}" \
|
||||
"${CLI_MAIN}"
|
||||
|
||||
if [[ "${goos}" != "windows" ]]; then
|
||||
chmod +x "${output}"
|
||||
fi
|
||||
}
|
||||
|
||||
build_one linux amd64 ""
|
||||
build_one linux arm64 ""
|
||||
build_one darwin amd64 ""
|
||||
build_one darwin arm64 ""
|
||||
build_one windows amd64 ".exe"
|
||||
build_one windows arm64 ".exe"
|
||||
|
||||
if command -v ldd >/dev/null 2>&1; then
|
||||
if ldd "${DIST_DIR}/${CLI_NAME}-${RELEASE_TAG}-linux-amd64" 2>&1 | grep -q "not a dynamic executable"; then
|
||||
echo "Verified linux/amd64 CLI is not dynamically linked"
|
||||
else
|
||||
echo "::error::linux/amd64 CLI is dynamically linked"
|
||||
ldd "${DIST_DIR}/${CLI_NAME}-${RELEASE_TAG}-linux-amd64" || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "${DIST_DIR}"
|
||||
sha256sum * > SHA256SUMS
|
||||
cd -
|
||||
|
||||
echo "Generated CLI release assets:"
|
||||
ls -lh "${DIST_DIR}"
|
||||
|
||||
- name: Upload Go CLI release assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # Use the secret as an environment variable
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prerelease: ${{ env.PRERELEASE }}
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
# The body field does not support environment variable substitution directly.
|
||||
body_path: release_body.md
|
||||
files: |
|
||||
dist/cli/*
|
||||
install.sh
|
||||
install.ps1
|
||||
|
||||
- name: Build and push image
|
||||
run: |
|
||||
|
||||
99
tools/scripts/INSTALL_SCRIPTS_README.md
Normal file
99
tools/scripts/INSTALL_SCRIPTS_README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# RAGFlow CLI Installation Scripts
|
||||
|
||||
RAGFlow publishes static Go CLI binaries as GitHub Release assets for:
|
||||
|
||||
- Linux: `amd64`, `arm64`
|
||||
- macOS: `amd64`, `arm64`
|
||||
- Windows: `amd64`, `arm64`
|
||||
|
||||
The release workflow builds the binaries with `CGO_ENABLED=0`, uploads `SHA256SUMS`, and uploads `install.sh` and `install.ps1`.
|
||||
|
||||
## Linux And macOS
|
||||
|
||||
Use the hosted script:
|
||||
|
||||
```sh
|
||||
curl -sSfL https://your-domain/install.sh | sh
|
||||
```
|
||||
|
||||
Install a specific version:
|
||||
|
||||
```sh
|
||||
curl -sSfL https://your-domain/install.sh | VERSION=v1.0.0 sh
|
||||
```
|
||||
|
||||
Install to a user-writable directory:
|
||||
|
||||
```sh
|
||||
curl -sSfL https://your-domain/install.sh | INSTALL_DIR="$HOME/.local/bin" sh
|
||||
```
|
||||
|
||||
The Unix installer:
|
||||
|
||||
- detects `linux` or `darwin`
|
||||
- detects `amd64` or `arm64`
|
||||
- downloads `ragflow_cli-{VERSION}-{OS}-{ARCH}`
|
||||
- verifies the file with the release `SHA256SUMS`
|
||||
- installs to `/usr/local/bin` by default
|
||||
|
||||
## Windows PowerShell
|
||||
|
||||
Use the hosted script:
|
||||
|
||||
```powershell
|
||||
iwr https://your-domain/install.ps1 -OutFile install.ps1
|
||||
powershell -ExecutionPolicy Bypass -File .\install.ps1
|
||||
```
|
||||
Install a specific version:
|
||||
|
||||
```powershell
|
||||
.\install.ps1 -Version "v1.0.0"
|
||||
```
|
||||
|
||||
Install to a custom directory:
|
||||
|
||||
```powershell
|
||||
.\install.ps1 -InstallDir "$env:USERPROFILE\bin"
|
||||
```
|
||||
|
||||
The Windows installer:
|
||||
|
||||
- detects `windows/amd64` or `windows/arm64`
|
||||
- downloads `ragflow_cli-{VERSION}-windows-{ARCH}.exe`
|
||||
- verifies the file with the release `SHA256SUMS`
|
||||
- installs to `$env:LOCALAPPDATA\Programs\RAGFlow` by default
|
||||
- adds the install directory to the user `PATH`
|
||||
|
||||
## Release Asset Names
|
||||
|
||||
The install scripts expect these names:
|
||||
|
||||
```text
|
||||
ragflow_cli-v1.0.0-linux-amd64
|
||||
ragflow_cli-v1.0.0-linux-arm64
|
||||
ragflow_cli-v1.0.0-darwin-amd64
|
||||
ragflow_cli-v1.0.0-darwin-arm64
|
||||
ragflow_cli-v1.0.0-windows-amd64.exe
|
||||
ragflow_cli-v1.0.0-windows-arm64.exe
|
||||
SHA256SUMS
|
||||
install.sh
|
||||
install.ps1
|
||||
```
|
||||
|
||||
## Hosting Options
|
||||
|
||||
Use a static domain for the public one-line command:
|
||||
|
||||
```text
|
||||
https://your-domain/install.sh
|
||||
https://your-domain/install.ps1
|
||||
```
|
||||
|
||||
The same scripts can also be used directly from a GitHub Release asset, for example:
|
||||
|
||||
```text
|
||||
https://github.com/infiniflow/ragflow/releases/download/v1.0.0/install.sh
|
||||
https://github.com/infiniflow/ragflow/releases/download/v1.0.0/install.ps1
|
||||
```
|
||||
|
||||
By default, both scripts install the latest stable GitHub Release. Set `VERSION` on Unix or `-Version` on Windows to pin a specific release.
|
||||
232
tools/scripts/install.ps1
Normal file
232
tools/scripts/install.ps1
Normal file
@@ -0,0 +1,232 @@
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
param(
|
||||
[string]$Version = "latest",
|
||||
[string]$InstallDir = "$env:LOCALAPPDATA\Programs\RAGFlow",
|
||||
[string]$GitHubRepo = "infiniflow/ragflow",
|
||||
[string]$CliName = "ragflow_cli"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "[INFO] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warn {
|
||||
param([string]$Message)
|
||||
Write-Host "[WARN] $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-ErrorMessage {
|
||||
param([string]$Message)
|
||||
Write-Host "[ERROR] $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Get-Platform {
|
||||
$os = "windows"
|
||||
$arch = "amd64"
|
||||
|
||||
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64" -or $env:PROCESSOR_ARCHITEW6432 -eq "ARM64") {
|
||||
$arch = "arm64"
|
||||
}
|
||||
elseif ([Environment]::Is64BitOperatingSystem) {
|
||||
$arch = "amd64"
|
||||
}
|
||||
else {
|
||||
Write-ErrorMessage "Unsupported Windows architecture: 386"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Info "Detected platform: ${os}/${arch}"
|
||||
|
||||
return @{
|
||||
OS = $os
|
||||
Arch = $arch
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ReleaseVersion {
|
||||
param(
|
||||
[string]$RequestedVersion,
|
||||
[string]$Repository
|
||||
)
|
||||
|
||||
if ($RequestedVersion -ne "latest") {
|
||||
Write-Info "Using specified version: $RequestedVersion"
|
||||
return $RequestedVersion
|
||||
}
|
||||
|
||||
Write-Info "Fetching latest release information"
|
||||
|
||||
$releaseUrl = "https://api.github.com/repos/${Repository}/releases/latest"
|
||||
$release = Invoke-RestMethod -Uri $releaseUrl -TimeoutSec 20
|
||||
$latestVersion = $release.tag_name
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($latestVersion)) {
|
||||
Write-ErrorMessage "Could not determine latest version"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Info "Latest version: $latestVersion"
|
||||
return $latestVersion
|
||||
}
|
||||
|
||||
function Get-DownloadInfo {
|
||||
param(
|
||||
[string]$ResolvedVersion,
|
||||
[string]$OS,
|
||||
[string]$Arch,
|
||||
[string]$Repository,
|
||||
[string]$BinaryName
|
||||
)
|
||||
|
||||
$fileName = "${BinaryName}-${ResolvedVersion}-${OS}-${Arch}.exe"
|
||||
$baseUrl = "https://github.com/${Repository}/releases/download/${ResolvedVersion}"
|
||||
|
||||
return @{
|
||||
FileName = $fileName
|
||||
BinaryUrl = "${baseUrl}/${fileName}"
|
||||
ChecksumUrl = "${baseUrl}/SHA256SUMS"
|
||||
}
|
||||
}
|
||||
|
||||
function Download-File {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$OutputPath
|
||||
)
|
||||
|
||||
Write-Info "Downloading $Url"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
Invoke-WebRequest -Uri $Url -OutFile $OutputPath -TimeoutSec 120
|
||||
}
|
||||
|
||||
function Test-Checksum {
|
||||
param(
|
||||
[string]$BinaryPath,
|
||||
[string]$ChecksumPath,
|
||||
[string]$FileName
|
||||
)
|
||||
|
||||
$checksumLine = Get-Content $ChecksumPath | Where-Object {
|
||||
$parts = $_ -split "\s+"
|
||||
$parts.Count -ge 2 -and $parts[1] -eq $FileName
|
||||
} | Select-Object -First 1
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($checksumLine)) {
|
||||
Write-ErrorMessage "No checksum found for $FileName in SHA256SUMS"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$expected = ($checksumLine -split "\s+")[0].ToLowerInvariant()
|
||||
$actual = (Get-FileHash -Algorithm SHA256 -Path $BinaryPath).Hash.ToLowerInvariant()
|
||||
|
||||
if ($actual -ne $expected) {
|
||||
Write-ErrorMessage "Checksum verification failed for $FileName"
|
||||
Write-ErrorMessage "Expected: $expected"
|
||||
Write-ErrorMessage "Actual: $actual"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Info "Checksum verified"
|
||||
}
|
||||
|
||||
function Install-CLI {
|
||||
param(
|
||||
[string]$TempFile,
|
||||
[string]$TargetDir,
|
||||
[string]$BinaryName
|
||||
)
|
||||
|
||||
if (-not (Test-Path $TargetDir)) {
|
||||
Write-Info "Creating directory: $TargetDir"
|
||||
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$targetFile = Join-Path $TargetDir "${BinaryName}.exe"
|
||||
Write-Info "Installing CLI to $targetFile"
|
||||
|
||||
Stop-Process -Name $BinaryName -Force -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path $TempFile -Destination $targetFile -Force
|
||||
|
||||
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$pathParts = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($userPath)) {
|
||||
$pathParts = $userPath -split ";"
|
||||
}
|
||||
|
||||
if ($pathParts -notcontains $TargetDir) {
|
||||
Write-Info "Adding $TargetDir to user PATH"
|
||||
$newPath = if ([string]::IsNullOrWhiteSpace($userPath)) { $TargetDir } else { "$userPath;$TargetDir" }
|
||||
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
|
||||
Write-Warn "Restart your terminal for PATH changes to take effect"
|
||||
}
|
||||
|
||||
Write-Info "CLI installed successfully at $targetFile"
|
||||
return $targetFile
|
||||
}
|
||||
|
||||
function Test-Installation {
|
||||
param([string]$CliPath)
|
||||
|
||||
if (-not (Test-Path $CliPath)) {
|
||||
Write-Warn "CLI not found at expected location: $CliPath"
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
& $CliPath --version
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Info "Installation verified successfully"
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warn "Could not execute version check: $_"
|
||||
}
|
||||
|
||||
Write-Warn "Could not verify CLI execution, but the binary was installed"
|
||||
}
|
||||
|
||||
function Main {
|
||||
$platform = Get-Platform
|
||||
$resolvedVersion = Get-ReleaseVersion -RequestedVersion $Version -Repository $GitHubRepo
|
||||
$downloadInfo = Get-DownloadInfo -ResolvedVersion $resolvedVersion -OS $platform.OS -Arch $platform.Arch -Repository $GitHubRepo -BinaryName $CliName
|
||||
|
||||
Write-Info "Download URL: $($downloadInfo.BinaryUrl)"
|
||||
|
||||
$tempBinary = [System.IO.Path]::GetTempFileName()
|
||||
$tempSums = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
Download-File -Url $downloadInfo.BinaryUrl -OutputPath $tempBinary
|
||||
Download-File -Url $downloadInfo.ChecksumUrl -OutputPath $tempSums
|
||||
Test-Checksum -BinaryPath $tempBinary -ChecksumPath $tempSums -FileName $downloadInfo.FileName
|
||||
|
||||
$cliPath = Install-CLI -TempFile $tempBinary -TargetDir $InstallDir -BinaryName $CliName
|
||||
Test-Installation -CliPath $cliPath
|
||||
Write-Info "Installation complete"
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tempBinary -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $tempSums -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Main
|
||||
272
tools/scripts/install.sh
Executable file
272
tools/scripts/install.sh
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
set -eu
|
||||
|
||||
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
|
||||
GITHUB_REPO="${GITHUB_REPO:-infiniflow/ragflow}"
|
||||
CLI_NAME="${CLI_NAME:-ragflow_cli}"
|
||||
VERSION="${VERSION:-latest}"
|
||||
|
||||
RELEASE_API="https://api.github.com/repos/${GITHUB_REPO}/releases/latest"
|
||||
RELEASE_BASE_URL=""
|
||||
OS=""
|
||||
ARCH=""
|
||||
FILENAME=""
|
||||
DOWNLOAD_URL=""
|
||||
CHECKSUM_URL=""
|
||||
|
||||
info() {
|
||||
printf '[INFO] %s\n' "$1" >&2
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[WARN] %s\n' "$1" >&2
|
||||
}
|
||||
|
||||
error() {
|
||||
printf '[ERROR] %s\n' "$1" >&2
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
error "$1 is required but not installed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_as_root() {
|
||||
if "$@" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
info "Requesting sudo permission for: $*"
|
||||
sudo "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
error "Permission denied and sudo is not available: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
detect_platform() {
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
|
||||
case "$os_name" in
|
||||
Linux)
|
||||
OS="linux"
|
||||
;;
|
||||
Darwin)
|
||||
OS="darwin"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported OS for install.sh: $os_name"
|
||||
error "Use install.ps1 on Windows PowerShell"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$arch_name" in
|
||||
x86_64|amd64)
|
||||
ARCH="amd64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
ARCH="arm64"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported architecture: $arch_name"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
info "Detected platform: ${OS}/${ARCH}"
|
||||
}
|
||||
|
||||
resolve_version() {
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
info "Using specified version: $VERSION"
|
||||
return
|
||||
fi
|
||||
|
||||
info "Fetching latest release information"
|
||||
response="$(curl -sSfL "$RELEASE_API")"
|
||||
VERSION="$(printf '%s\n' "$response" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
error "Could not determine latest version from GitHub"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Latest version: $VERSION"
|
||||
}
|
||||
|
||||
build_urls() {
|
||||
FILENAME="${CLI_NAME}-${VERSION}-${OS}-${ARCH}"
|
||||
RELEASE_BASE_URL="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}"
|
||||
DOWNLOAD_URL="${RELEASE_BASE_URL}/${FILENAME}"
|
||||
CHECKSUM_URL="${RELEASE_BASE_URL}/SHA256SUMS"
|
||||
|
||||
info "Download URL: $DOWNLOAD_URL"
|
||||
}
|
||||
|
||||
download_file() {
|
||||
url="$1"
|
||||
output="$2"
|
||||
|
||||
if ! curl -sSfL "$url" -o "$output"; then
|
||||
rm -f "$output"
|
||||
error "Failed to download $url"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
file_size() {
|
||||
stat -f%z "$1" 2>/dev/null || stat -c%s "$1" 2>/dev/null || printf '0'
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
return
|
||||
fi
|
||||
|
||||
error "sha256sum or shasum is required to verify the download"
|
||||
exit 1
|
||||
}
|
||||
|
||||
verify_checksum() {
|
||||
binary_file="$1"
|
||||
sums_file="$2"
|
||||
|
||||
expected="$(awk -v f="$FILENAME" '$2 == f {print $1}' "$sums_file" | head -n 1)"
|
||||
if [ -z "$expected" ]; then
|
||||
error "No checksum found for $FILENAME in SHA256SUMS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
actual="$(sha256_file "$binary_file")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
error "Checksum verification failed for $FILENAME"
|
||||
error "Expected: $expected"
|
||||
error "Actual: $actual"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Checksum verified"
|
||||
}
|
||||
|
||||
download_cli() {
|
||||
binary_file="$(mktemp)"
|
||||
sums_file="$(mktemp)"
|
||||
|
||||
info "Downloading CLI"
|
||||
download_file "$DOWNLOAD_URL" "$binary_file"
|
||||
|
||||
size="$(file_size "$binary_file")"
|
||||
if [ "$size" -lt 100000 ]; then
|
||||
warn "Downloaded file is unusually small (${size} bytes)"
|
||||
fi
|
||||
|
||||
info "Downloading SHA256SUMS"
|
||||
download_file "$CHECKSUM_URL" "$sums_file"
|
||||
verify_checksum "$binary_file" "$sums_file"
|
||||
rm -f "$sums_file"
|
||||
|
||||
printf '%s\n' "$binary_file"
|
||||
}
|
||||
|
||||
install_cli() {
|
||||
source_file="$1"
|
||||
target_file="${INSTALL_DIR}/${CLI_NAME}"
|
||||
|
||||
info "Installing CLI to $target_file"
|
||||
|
||||
if [ ! -d "$INSTALL_DIR" ]; then
|
||||
run_as_root mkdir -p "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
if [ -w "$INSTALL_DIR" ]; then
|
||||
mv "$source_file" "$target_file"
|
||||
chmod 755 "$target_file"
|
||||
else
|
||||
run_as_root mv "$source_file" "$target_file"
|
||||
run_as_root chmod 755 "$target_file"
|
||||
fi
|
||||
|
||||
info "CLI installed successfully at $target_file"
|
||||
}
|
||||
|
||||
verify_installation() {
|
||||
cli_path="${INSTALL_DIR}/${CLI_NAME}"
|
||||
|
||||
if [ ! -x "$cli_path" ]; then
|
||||
warn "CLI may not be executable: $cli_path"
|
||||
return
|
||||
fi
|
||||
|
||||
if "$cli_path" --version >/dev/null 2>&1; then
|
||||
"$cli_path" --version
|
||||
info "Installation verified successfully"
|
||||
return
|
||||
fi
|
||||
|
||||
if "$cli_path" -h >/dev/null 2>&1 || "$cli_path" --help >/dev/null 2>&1; then
|
||||
info "Installation verified successfully"
|
||||
return
|
||||
fi
|
||||
|
||||
warn "Could not verify CLI execution, but the binary was installed"
|
||||
}
|
||||
|
||||
print_path_notice() {
|
||||
case ":$PATH:" in
|
||||
*":$INSTALL_DIR:"*)
|
||||
;;
|
||||
*)
|
||||
warn "$INSTALL_DIR is not in PATH"
|
||||
warn "Add it with: export PATH=\"$INSTALL_DIR:\$PATH\""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main() {
|
||||
need_cmd curl
|
||||
need_cmd uname
|
||||
need_cmd mktemp
|
||||
need_cmd awk
|
||||
need_cmd sed
|
||||
|
||||
detect_platform
|
||||
resolve_version
|
||||
build_urls
|
||||
|
||||
temp_file="$(download_cli)"
|
||||
install_cli "$temp_file"
|
||||
verify_installation
|
||||
print_path_notice
|
||||
|
||||
info "Installation complete"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user