전체 설치 스크립트 GitHub 릴리스 에셋 검증 및 폴백 다운로드 방어 로직 적용

This commit is contained in:
mcchae committed 2026-10-31 04:54:34 +09:00
1 parent 5a67e7bbcf
commit b41d2f68f3
21 files changed
+507 -156

No files matched your search

@@ -0,0 +1,54 @@
# 전체 설치 스크립트 다운로드 안정성 강화 계획 (HARDEN-INSTALL-LINKS-IMP-PLAN)
## 1. 개요
외부 오픈소스 저장소의 릴리스 정책 변경(서브 크레이트 태그 발행, 빌드 진행 중 빈 에셋 상태, 파일명 변경) 및 GitHub API Rate Limit(시간당 60회 제한)으로 인해 설치 스크립트가 실패하는 문제를 방지하기 위해 모든 설치 스크립트(`install-*.sh`, `install-*.ps1`)에 **3단계 방어 로직(Asset-aware Lookup + Fallback Tag + URL Fallback)**을 일괄 적용합니다.
```mermaid
flowchart TD
Start([설치 스크립트 실행]) --> API[GitHub Releases API 조회<br/>User-Agent 헤더 포함]
API --> CheckAPI{API 응답 성공?}
CheckAPI -- Yes --> AssetSearch["릴리스 목록에서 현재 플랫폼 에셋이<br/>실제로 존재하는(assets > 0) 최신 릴리스 탐색"]
AssetSearch --> FoundAsset{에셋 URL 발견?}
FoundAsset -- Yes --> DownloadPrimary[1차: 에셋 URL 다운로드 시도]
FoundAsset -- No --> FallbackBuild[2차: FALLBACK_TAG 기반 URL 생성]
CheckAPI -- No (Rate Limit 등) --> FallbackBuild
DownloadPrimary --> CheckDL{다운로드 성공?}
CheckDL -- Yes --> ExtractInstall[압축 해제 및 바이너리 설치]
CheckDL -- No (404 등) --> FallbackBuild
FallbackBuild --> DownloadFallback[FALLBACK_TAG URL로 재시도]
DownloadFallback --> ExtractInstall
ExtractInstall --> Done([설치 완료])
```
## 2. 작업 단계 및 체크리스트
### 단계 1: 실행 계획 수립
- [x] 작업 계획서(`VIBE-WORK/261031/HARDEN-INSTALL-LINKS-IMP-PLAN.md`) 작성
### 단계 2: Unix / macOS용 Bash 설치 스크립트(`.sh`) 방어 로직 적용
- [x] `install-all-smi.sh`: 에셋 존재 검증, User-Agent 헤더, 다운로드 실패 시 `FALLBACK_VERSION` 자동 재시도 적용
- [x] `install-bottom.sh`: `assets` 배열 내 현재 플랫폼 파일(`bottom_${triple}.${ext}`) 존재 여부 확인 및 `FALLBACK_TAG="0.12.3"` / `"nightly"` 폴백 적용
- [x] `install-ctop.sh`: `assets` 존재 여부 필터링 및 `FALLBACK_TAG="v0.7.7"` 폴백 다운로드 적용
- [x] `install-dua.sh`: 다운로드 실패 시 `FALLBACK_TAG="v2.46.0"` 자동 재시도 보강
- [x] `install-helix.sh`: `/releases` 목록에서 `assets`가 존재하는 최신 릴리스 탐색 및 `FALLBACK_TAG="25.07.1"` 폴백 적용
- [x] `install-lazydocker.sh`: `assets` 존재 여부 필터링 및 `FALLBACK_TAG="v0.25.2"` 폴백 다운로드 적용
- [x] `install-lazygit.sh`: `assets` 존재 여부 필터링, 중복 함수 정리 및 `FALLBACK_TAG="v0.61.1"` 폴백 다운로드 적용
- [x] `install-ocr.sh`: 에셋 존재 검증 및 다운로드 실패 시 `FALLBACK_VERSION="v1.3.19"` 자동 재시도 적용
- [x] `install-ttyd.sh`: 에셋 존재 검증 및 `FALLBACK_TAG="1.7.7"` 폴백 다운로드 적용
### 단계 3: Windows용 PowerShell 설치 스크립트(`.ps1`) 방어 로직 적용
- [x] `install-all-smi.ps1`: 에셋 검증 및 다운로드 실패 시 `FALLBACK_VERSION` 자동 재시도 적용
- [x] `install-bottom.ps1`: 에셋 존재 검증 및 `FALLBACK_TAG` 폴백 다운로드 적용
- [x] `install-ctop.ps1`: 에셋 존재 검증 및 `FALLBACK_TAG="v0.7.7"` 폴백 다운로드 적용
- [x] `install-dua.ps1`: 다운로드 실패 시 `FALLBACK_TAG="v2.46.0"` 자동 재시도 보강
- [x] `install-helix.ps1`: API 실패 또는 에셋 미발견 시 `FALLBACK_TAG="25.07.1"` 폴백 다운로드 적용
- [x] `install-lazydocker.ps1`: 에셋 존재 검증 및 `FALLBACK_TAG="v0.25.2"` 폴백 다운로드 적용
- [x] `install-lazygit.ps1`: Git for Windows 및 lazygit 에셋 검증, `FALLBACK_TAG="v0.61.1"` 폴백 다운로드 적용
- [x] `install-ocr.ps1`: 다운로드 실패 시 `FALLBACK_VERSION="v1.3.19"` 자동 재시도 적용
- [x] `install-ttyd.ps1`: API 실패 또는 에셋 미발견 시 `FALLBACK_TAG="1.7.7"` 폴백 다운로드 적용
### 단계 4: 문법 검증, 완료 보고서 작성 및 Git 푸시
- [x] 모든 `.sh` 스크립트 `bash -n` 문법 검사 통과 확인
- [x] 완료 보고서(`VIBE-WORK/261031/HARDEN-INSTALL-LINKS-PHASE01.md`) 작성
- [x] `uv tool run --from graphifyy graphify update .` 실행
- [x] Git commit 및 push
@@ -0,0 +1,28 @@
# 전체 설치 스크립트 다운로드 안정성 강화 완료 보고서 (HARDEN-INSTALL-LINKS-PHASE01)
## 1. 개요
외부 오픈소스 저장소들의 릴리스 정책 변화(서브 크레이트 릴리스 혼재, CI 빌드 중 빈 에셋 상태, 파일명 변경)나 GitHub API Rate Limit(시간당 60회 제한)이 발생하더라도 모든 설치 스크립트가 중단 없이 정상 설치되도록 **3단계 방어 로직**을 전체 스크립트(`install-*.sh`, `install-*.ps1`)에 일괄 적용하였습니다.
```mermaid
flowchart LR
Step1["1단계: 에셋 존재 검증<br/>(assets 배열이 비어있지 않은<br/>릴리스만 선택)"] --> Step2["2단계: API 폴백<br/>(API Rate Limit/오류 시<br/>검증된 FALLBACK_TAG 사용)"]
Step2 --> Step3["3단계: 다운로드 폴백<br/>(최신 URL 404/실패 시<br/>FALLBACK_TAG URL로 자동 재시도)"]
```
## 2. 도구별 적용 내역 및 Fallback 버전
| 도구 | 대상 스크립트 | 에셋 필터링 조건 | 검증된 Fallback 태그 |
| :--- | :--- | :--- | :--- |
| **all-smi** | `install-all-smi.{sh,ps1}` | `assets.length > 0` | `v0.25.0` |
| **bottom** | `install-bottom.{sh,ps1}` | `assets.length > 0` (Nightly & Stable) | `0.12.3` |
| **ctop** | `install-ctop.{sh,ps1}` | `prerelease == false && assets.length > 0` | `v0.7.7` |
| **dua** | `install-dua.{sh,ps1}` | `prerelease == false && startswith("v") && assets.length > 0` | `v2.46.0` |
| **Helix** | `install-helix.{sh,ps1}` | `prerelease == false && assets.length > 0` | `25.07.1` |
| **lazydocker** | `install-lazydocker.{sh,ps1}` | `prerelease == false && assets.length > 0` | `v0.25.2` |
| **lazygit** | `install-lazygit.{sh,ps1}` | `prerelease == false && assets.length > 0` | `v0.61.1` |
| **ocr** | `install-ocr.{sh,ps1}` | `assets.length > 0` | `v1.3.19` |
| **ttyd** | `install-ttyd.{sh,ps1}` | `assets.length > 0` | `1.7.7` |
## 3. 추가 개선 사항
- `jq`와 `python3`가 모두 없는 최소 리눅스 컨테이너 환경에서도 `grep`/`sed` 폴백 및 `FALLBACK_TAG`를 통해 설치가 중단되지 않도록 개선했습니다.
- 모든 GitHub API 요청에 `User-Agent` 헤더와 타임아웃(`--connect-timeout 10` / `-TimeoutSec 15`)을 명시하여 무한 대기 현상을 방지했습니다.
+13 -1
View File
@@ -51,8 +51,10 @@ if ($arch -ne "AMD64" -and $arch -ne "ARM64") {
$tag = $null $tag = $null
try { try {
Info "Fetching latest release tag from GitHub API..." Info "Fetching latest release tag from GitHub API..."
$response = Invoke-RestMethod -Uri $API_URL -TimeoutSec 10 $response = Invoke-RestMethod -Uri $API_URL -Headers @{ 'User-Agent' = 'install-all-smi-ps1' } -TimeoutSec 10
if ($response -and $response.assets -and $response.assets.Count -gt 0) {
$tag = $response.tag_name $tag = $response.tag_name
}
} catch { } catch {
Warn "Failed to retrieve the latest version from GitHub API. Using fallback version: $FALLBACK_VERSION" Warn "Failed to retrieve the latest version from GitHub API. Using fallback version: $FALLBACK_VERSION"
$tag = $FALLBACK_VERSION $tag = $FALLBACK_VERSION
@@ -63,6 +65,7 @@ if (-not $tag) {
} }
$downloadUrl = "https://github.com/$REPO/releases/download/$tag/$assetName" $downloadUrl = "https://github.com/$REPO/releases/download/$tag/$assetName"
$fallbackUrl = "https://github.com/$REPO/releases/download/$FALLBACK_VERSION/$assetName"
Info "Resolved download URL: $downloadUrl" Info "Resolved download URL: $downloadUrl"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -80,7 +83,16 @@ $destFile = Join-Path $INSTALL_DIR "all-smi.exe"
try { try {
Info "Downloading binary archive..." Info "Downloading binary archive..."
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempZip -UseBasicParsing Invoke-WebRequest -Uri $downloadUrl -OutFile $tempZip -UseBasicParsing
} catch {
if ($downloadUrl -ne $fallbackUrl) {
Warn "Primary download failed. Retrying with fallback version ($FALLBACK_VERSION): $fallbackUrl"
Invoke-WebRequest -Uri $fallbackUrl -OutFile $tempZip -UseBasicParsing
} else {
throw
}
}
if (Test-Path $tempExtractDir) { if (Test-Path $tempExtractDir) {
Remove-Item $tempExtractDir -Recurse -Force Remove-Item $tempExtractDir -Recurse -Force
+9 -3
View File
@@ -71,9 +71,9 @@ get_latest_tag() {
local json tag local json tag
info "GitHub API에서 all-smi 최신 릴리즈 정보를 가져오는 중..." info "GitHub API에서 all-smi 최신 릴리즈 정보를 가져오는 중..."
if json=$(curl -fsSL --connect-timeout 10 "${API_URL}" 2>/dev/null); then if json=$(curl -fsSL -H "User-Agent: install-all-smi-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null); then
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
tag=$(echo "$json" | jq -r '.tag_name') tag=$(echo "$json" | jq -r 'select((.assets | length) > 0) | .tag_name')
else else
# jq가 없는 경우 grep & sed 사용 # jq가 없는 경우 grep & sed 사용
tag=$(echo "$json" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/') tag=$(echo "$json" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')
@@ -92,7 +92,7 @@ get_latest_tag() {
# 메인 설치 함수 # 메인 설치 함수
# ──────────────────────────────────────────── # ────────────────────────────────────────────
main() { main() {
local platform_info asset_name format tag url tmpdir archive_file bin_src local platform_info asset_name format tag url fallback_url tmpdir archive_file bin_src
platform_info="$(detect_platform)" platform_info="$(detect_platform)"
asset_name="${platform_info%%:*}" asset_name="${platform_info%%:*}"
@@ -104,6 +104,7 @@ main() {
info "적용할 버전 태그: $tag" info "적용할 버전 태그: $tag"
url="https://github.com/${REPO}/releases/download/${tag}/${asset_name}" url="https://github.com/${REPO}/releases/download/${tag}/${asset_name}"
fallback_url="https://github.com/${REPO}/releases/download/${FALLBACK_VERSION}/${asset_name}"
# 임시 디렉토리 생성 # 임시 디렉토리 생성
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -113,8 +114,13 @@ main() {
info "바이너리 아카이브 다운로드 중: $url" info "바이너리 아카이브 다운로드 중: $url"
if ! curl -fsSL --progress-bar -o "$archive_file" "$url"; then if ! curl -fsSL --progress-bar -o "$archive_file" "$url"; then
if [[ "$tag" != "$FALLBACK_VERSION" ]]; then
warn "최신 버전(${tag}) 다운로드 실패. 검증된 폴백 버전(${FALLBACK_VERSION})으로 재시도합니다: $fallback_url"
curl -fsSL --progress-bar -o "$archive_file" "$fallback_url" || error "다운로드 실패: $fallback_url"
else
error "다운로드 실패: $url" error "다운로드 실패: $url"
fi fi
fi
info "압축 해제 중..." info "압축 해제 중..."
case "$format" in case "$format" in
+28 -15
View File
@@ -1,4 +1,4 @@
#Requires -Version 5.1 #Requires -Version 5.1
<# <#
.SYNOPSIS .SYNOPSIS
Installs bottom(btm) process monitor from GitHub Nightly releases. Installs bottom(btm) process monitor from GitHub Nightly releases.
@@ -48,6 +48,7 @@ $ErrorActionPreference = 'Stop'
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "ClementTsang/bottom" $REPO = "ClementTsang/bottom"
$API_URL = "https://api.github.com/repos/$REPO/releases" $API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "0.12.3"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
# Utility functions # Utility functions
@@ -98,25 +99,26 @@ function Get-Platform {
# Get latest Nightly release tag # Get latest Nightly release tag
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Get-LatestNightlyTag { function Get-LatestNightlyTag {
Write-Info "Fetching latest Nightly release from GitHub API..." Write-Info "Fetching latest release from GitHub API..."
try { try {
$releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing `
-Headers @{ 'User-Agent' = 'install-bottom-ps1' } -Headers @{ 'User-Agent' = 'install-bottom-ps1' } -TimeoutSec 15
} catch {
Write-Err "Failed to call GitHub API: $_"
}
# Get first item where prerelease=true and tag_name starts with 'nightly-'
$nightly = $releases | Where-Object { $nightly = $releases | Where-Object {
$_.prerelease -eq $true -and $_.tag_name -like 'nightly-*' $_.assets -and $_.assets.Count -gt 0 -and
(($_.prerelease -eq $true -and $_.tag_name -like 'nightly-*') -or $_.prerelease -eq $false)
} | Select-Object -First 1 } | Select-Object -First 1
if (-not $nightly) { if ($nightly -and $nightly.tag_name) {
Write-Err "Could not find Nightly release." return $nightly.tag_name
}
} catch {
Write-Warn "GitHub API lookup failed: $_"
} }
return $nightly.tag_name Write-Warn "Using fallback release tag: $FALLBACK_TAG"
return $FALLBACK_TAG
} }
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -132,7 +134,7 @@ function Get-DownloadUrl {
# Download, extract & install # Download, extract & install
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Install-Bottom { function Install-Bottom {
param([string]$Url) param([string]$Url, [string]$FallbackUrl)
# Create temp directory # Create temp directory
$tmpDir = Join-Path $env:TEMP "bottom_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "bottom_install_$(Get-Random)"
@@ -143,13 +145,23 @@ function Install-Bottom {
Write-Info "Downloading: $Url" Write-Info "Downloading: $Url"
try { try {
# Suppress progress bar in PowerShell 5+
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
} catch {
if ($FallbackUrl -and ($FallbackUrl -ne $Url)) {
Write-Warn "Primary download failed. Retrying with fallback URL: $FallbackUrl"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $FallbackUrl -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch { } catch {
Write-Err "Download failed: $_" Write-Err "Download failed: $_"
} }
} else {
Write-Err "Download failed: $_"
}
}
Write-Info "Extracting..." Write-Info "Extracting..."
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
@@ -220,10 +232,11 @@ function Main {
Write-Info "Detected platform: $triple" Write-Info "Detected platform: $triple"
$tag = Get-LatestNightlyTag $tag = Get-LatestNightlyTag
Write-Info "Latest Nightly tag: $tag" Write-Info "Release tag: $tag"
$url = Get-DownloadUrl -Tag $tag -Triple $triple $url = Get-DownloadUrl -Tag $tag -Triple $triple
Install-Bottom -Url $url $fallbackUrl = Get-DownloadUrl -Tag $FALLBACK_TAG -Triple $triple
Install-Bottom -Url $url -FallbackUrl $fallbackUrl
if ($AddToPath) { if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir Add-ToUserPath -Dir $InstallDir
+33 -15
View File
@@ -18,6 +18,7 @@ set -euo pipefail
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="ClementTsang/bottom" REPO="ClementTsang/bottom"
API_URL="https://api.github.com/repos/${REPO}/releases" API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="0.12.3"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -35,23 +36,26 @@ for cmd in curl uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다." command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다."
done done
# Fallback: use python3 if jq is not available # Fallback: use python3 or grep if jq is not available
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_nightly_tag() { _json_nightly_tag() {
jq -r '[.[] | select(.prerelease==true and (.tag_name | startswith("nightly-")))] | .[0].tag_name' jq -r '[.[] | select((.assets | length) > 0 and ((.prerelease==true and (.tag_name | startswith("nightly-"))) or .prerelease==false))] | .[0].tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' or 'python3' is required."
_json_nightly_tag() { _json_nightly_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
for r in data: for r in data:
if r.get('prerelease') and r.get('tag_name','').startswith('nightly-'): if r.get('assets') and ((r.get('prerelease') and r.get('tag_name','').startswith('nightly-')) or not r.get('prerelease')):
print(r['tag_name']) print(r['tag_name'])
break break
" "
} }
else
_json_nightly_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -131,15 +135,19 @@ detect_platform() {
# Get latest Nightly release tag # Get latest Nightly release tag
# ──────────────────────────────────────────── # ────────────────────────────────────────────
get_latest_nightly_tag() { get_latest_nightly_tag() {
info "Fetching latest Nightly release from GitHub API..." info "Fetching latest release from GitHub API..."
local releases local releases="" tag=""
releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." releases="$(curl -fsSL -H "User-Agent: install-bottom-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
local tag if [[ -n "$releases" ]]; then
tag="$(echo "$releases" | _json_nightly_tag)" tag="$(echo "$releases" | _json_nightly_tag 2>/dev/null || true)"
fi
[[ -z "$tag" || "$tag" == "null" ]] && error "Could not find Nightly release tag." if [[ -z "$tag" || "$tag" == "null" ]]; then
warn "Could not fetch release tag from GitHub API. Using fallback tag: ${FALLBACK_TAG}"
tag="${FALLBACK_TAG}"
fi
echo "$tag" echo "$tag"
} }
@@ -161,6 +169,7 @@ download_and_install() {
local url="$1" local url="$1"
local ext="$2" # tar.gz | zip local ext="$2" # tar.gz | zip
local is_windows="${3:-false}" local is_windows="${3:-false}"
local fallback_url="${4:-}"
local tmpdir local tmpdir
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -170,8 +179,15 @@ download_and_install() {
filename="${url##*/}" filename="${url##*/}"
info "Downloading: $url" info "Downloading: $url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" \ if ! curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url"; then
|| error "Download failed: $url" if [[ -n "$fallback_url" && "$fallback_url" != "$url" ]]; then
warn "Primary download failed. Retrying with fallback URL: $fallback_url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$fallback_url" \
|| error "Download failed: $fallback_url"
else
error "Download failed: $url"
fi
fi
info "Extracting..." info "Extracting..."
case "$ext" in case "$ext" in
@@ -239,10 +255,12 @@ main() {
[[ "$triple" == *"windows"* ]] && is_windows="true" [[ "$triple" == *"windows"* ]] && is_windows="true"
tag="$(get_latest_nightly_tag)" tag="$(get_latest_nightly_tag)"
info "Latest Nightly tag: $tag" info "Release tag: $tag"
url="$(build_download_url "$tag" "$triple" "$ext")" url="$(build_download_url "$tag" "$triple" "$ext")"
download_and_install "$url" "$ext" "$is_windows" local fallback_url
fallback_url="$(build_download_url "$FALLBACK_TAG" "$triple" "$ext")"
download_and_install "$url" "$ext" "$is_windows" "$fallback_url"
} }
main "$@" main "$@"
+28 -12
View File
@@ -1,4 +1,4 @@
#Requires -Version 5.1 #Requires -Version 5.1
<# <#
.SYNOPSIS .SYNOPSIS
Installs ctop (container terminal UI) from GitHub releases. Installs ctop (container terminal UI) from GitHub releases.
@@ -49,6 +49,7 @@ $ErrorActionPreference = 'Stop'
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "bcicen/ctop" $REPO = "bcicen/ctop"
$API_URL = "https://api.github.com/repos/$REPO/releases" $API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "v0.7.7"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
# Utility functions # Utility functions
@@ -106,18 +107,21 @@ function Get-LatestTag {
try { try {
$releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing `
-Headers @{ 'User-Agent' = 'install-ctop-ps1' } -Headers @{ 'User-Agent' = 'install-ctop-ps1' } -TimeoutSec 15
} catch {
Write-Err "Failed to call GitHub API: $_"
}
$latest = $releases | Where-Object { $_.prerelease -eq $false } | Select-Object -First 1 $latest = $releases | Where-Object {
$_.prerelease -eq $false -and $_.assets -and $_.assets.Count -gt 0
if (-not $latest) { } | Select-Object -First 1
Write-Err "Could not find latest release."
}
if ($latest -and $latest.tag_name) {
return $latest.tag_name return $latest.tag_name
}
} catch {
Write-Warn "GitHub API lookup failed: $_"
}
Write-Warn "Using fallback release tag: $FALLBACK_TAG"
return $FALLBACK_TAG
} }
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -135,7 +139,7 @@ function Get-DownloadUrl {
# Download & install (raw binary — no archive extraction needed) # Download & install (raw binary — no archive extraction needed)
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Install-Ctop { function Install-Ctop {
param([string]$Url) param([string]$Url, [string]$FallbackUrl)
$tmpDir = Join-Path $env:TEMP "ctop_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "ctop_install_$(Get-Random)"
New-Item -ItemType Directory -Path $tmpDir | Out-Null New-Item -ItemType Directory -Path $tmpDir | Out-Null
@@ -148,9 +152,20 @@ function Install-Ctop {
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Url -OutFile $binPath -UseBasicParsing Invoke-WebRequest -Uri $Url -OutFile $binPath -UseBasicParsing
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
} catch {
if ($FallbackUrl -and ($FallbackUrl -ne $Url)) {
Write-Warn "Primary download failed. Retrying with fallback URL: $FallbackUrl"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $FallbackUrl -OutFile $binPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch { } catch {
Write-Err "Download failed: $_" Write-Err "Download failed: $_"
} }
} else {
Write-Err "Download failed: $_"
}
}
# Create install directory # Create install directory
if (-not (Test-Path $InstallDir)) { if (-not (Test-Path $InstallDir)) {
@@ -210,7 +225,8 @@ function Main {
Write-Info "Latest release: $tag" Write-Info "Latest release: $tag"
$url = Get-DownloadUrl -Tag $tag -Platform $platform $url = Get-DownloadUrl -Tag $tag -Platform $platform
Install-Ctop -Url $url $fallbackUrl = Get-DownloadUrl -Tag $FALLBACK_TAG -Platform $platform
Install-Ctop -Url $url -FallbackUrl $fallbackUrl
if ($AddToPath) { if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir Add-ToUserPath -Dir $InstallDir
+31 -13
View File
@@ -16,6 +16,7 @@ set -euo pipefail
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="bcicen/ctop" REPO="bcicen/ctop"
API_URL="https://api.github.com/repos/${REPO}/releases" API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="v0.7.7"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -33,23 +34,26 @@ for cmd in curl uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' command is required." command -v "$cmd" &>/dev/null || error "'$cmd' command is required."
done done
# Fallback: use python3 if jq is not available # Fallback: use python3 or grep if jq is not available
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_latest_tag() { _json_latest_tag() {
jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name' jq -r '[.[] | select(.prerelease==false and ((.assets | length) > 0))] | .[0].tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' or 'python3' is required."
_json_latest_tag() { _json_latest_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
for r in data: for r in data:
if not r.get('prerelease'): if not r.get('prerelease') and r.get('assets'):
print(r['tag_name']) print(r['tag_name'])
break break
" "
} }
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -118,13 +122,17 @@ detect_platform() {
get_latest_tag() { get_latest_tag() {
info "Fetching latest release from GitHub API..." info "Fetching latest release from GitHub API..."
local releases local releases="" tag=""
releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." releases="$(curl -fsSL -H "User-Agent: install-ctop-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
local tag if [[ -n "$releases" ]]; then
tag="$(echo "$releases" | _json_latest_tag)" tag="$(echo "$releases" | _json_latest_tag 2>/dev/null || true)"
fi
[[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag." if [[ -z "$tag" || "$tag" == "null" ]]; then
warn "Could not fetch latest release tag from GitHub API. Using fallback tag: ${FALLBACK_TAG}"
tag="${FALLBACK_TAG}"
fi
echo "$tag" echo "$tag"
} }
@@ -146,6 +154,7 @@ build_download_url() {
# ──────────────────────────────────────────── # ────────────────────────────────────────────
download_and_install() { download_and_install() {
local url="$1" local url="$1"
local fallback_url="${2:-}"
local tmpdir local tmpdir
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -155,8 +164,15 @@ download_and_install() {
filename="${url##*/}" filename="${url##*/}"
info "Downloading: $url" info "Downloading: $url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" \ if ! curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url"; then
|| error "Download failed: $url" if [[ -n "$fallback_url" && "$fallback_url" != "$url" ]]; then
warn "Primary download failed. Retrying with fallback URL: $fallback_url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$fallback_url" \
|| error "Download failed: $fallback_url"
else
error "Download failed: $url"
fi
fi
# ctop provides raw binaries (not archives), so skip extraction # ctop provides raw binaries (not archives), so skip extraction
local bin_src="${tmpdir}/${filename}" local bin_src="${tmpdir}/${filename}"
@@ -194,7 +210,9 @@ main() {
info "Latest release: $tag" info "Latest release: $tag"
url="$(build_download_url "$tag" "$platform")" url="$(build_download_url "$tag" "$platform")"
download_and_install "$url" local fallback_url
fallback_url="$(build_download_url "$FALLBACK_TAG" "$platform")"
download_and_install "$url" "$fallback_url"
} }
main "$@" main "$@"
+14 -2
View File
@@ -127,7 +127,7 @@ function Get-DownloadUrl {
# Download, extract & install # Download, extract & install
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Install-Dua { function Install-Dua {
param([string]$Url) param([string]$Url, [string]$FallbackUrl)
$tmpDir = Join-Path $env:TEMP "dua_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "dua_install_$(Get-Random)"
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
@@ -141,8 +141,19 @@ function Install-Dua {
Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
} catch { } catch {
if ($FallbackUrl -and ($FallbackUrl -ne $Url)) {
Warn "Primary download failed. Retrying with fallback URL: $FallbackUrl"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $FallbackUrl -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch {
ErrExit "Download failed from $FallbackUrl : $_"
}
} else {
ErrExit "Download failed from $Url : $_" ErrExit "Download failed from $Url : $_"
} }
}
Info "Extracting archive..." Info "Extracting archive..."
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
@@ -218,7 +229,8 @@ function Main {
Info "Release tag: $tag" Info "Release tag: $tag"
$url = Get-DownloadUrl -Tag $tag -Triple $triple $url = Get-DownloadUrl -Tag $tag -Triple $triple
$duaBin = Install-Dua -Url $url $fallbackUrl = Get-DownloadUrl -Tag $FALLBACK_TAG -Triple $triple
$duaBin = Install-Dua -Url $url -FallbackUrl $fallbackUrl
if ($AddToPath) { if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir Add-ToUserPath -Dir $InstallDir
+10 -1
View File
@@ -145,13 +145,22 @@ install_from_github() {
tag="$(get_latest_tag)" tag="$(get_latest_tag)"
local filename="dua-${tag}-${target}.tar.gz" local filename="dua-${tag}-${target}.tar.gz"
local download_url="https://github.com/${REPO}/releases/download/${tag}/${filename}" local download_url="https://github.com/${REPO}/releases/download/${tag}/${filename}"
local fallback_filename="dua-${FALLBACK_TAG}-${target}.tar.gz"
local fallback_url="https://github.com/${REPO}/releases/download/${FALLBACK_TAG}/${fallback_filename}"
info "Downloading: $download_url" info "Downloading: $download_url"
TMPDIR="$(mktemp -d)" TMPDIR="$(mktemp -d)"
trap '[[ -d "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"' EXIT trap '[[ -d "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"' EXIT
curl -fsSL --progress-bar -o "${TMPDIR}/${filename}" "$download_url" || if ! curl -fsSL --progress-bar -o "${TMPDIR}/${filename}" "$download_url"; then
if [[ "$tag" != "$FALLBACK_TAG" ]]; then
warn "Primary download failed. Retrying with fallback tag (${FALLBACK_TAG}): $fallback_url"
curl -fsSL --progress-bar -o "${TMPDIR}/${filename}" "$fallback_url" ||
error "Download failed: $fallback_url"
else
error "Download failed: $download_url" error "Download failed: $download_url"
fi
fi
info "Extracting..." info "Extracting..."
tar -xzf "${TMPDIR}/${filename}" -C "$TMPDIR" tar -xzf "${TMPDIR}/${filename}" -C "$TMPDIR"
+28 -8
View File
@@ -58,7 +58,8 @@ function Write-Err { param([string]$msg) Write-Host "[ERROR] $msg" -Foregrou
# 상수 정의 # 상수 정의
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "helix-editor/helix" $REPO = "helix-editor/helix"
$API_URL = "https://api.github.com/repos/$REPO/releases/latest" $API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "25.07.1"
$HELIX_CONFIG_DIR = Join-Path $env:APPDATA "helix" $HELIX_CONFIG_DIR = Join-Path $env:APPDATA "helix"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -76,27 +77,37 @@ function Main {
# 2) GitHub 최신 릴리스 정보 획득 # 2) GitHub 최신 릴리스 정보 획득
Write-Info "Fetching latest Helix release information from GitHub API..." Write-Info "Fetching latest Helix release information from GitHub API..."
$release = $null
$tagName = $FALLBACK_TAG
try { try {
$release = Invoke-RestMethod -Uri $API_URL -UseBasicParsing -Headers @{ 'User-Agent' = 'install-helix-ps1' } $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing -Headers @{ 'User-Agent' = 'install-helix-ps1' } -TimeoutSec 15
$release = $releases | Where-Object { $_.prerelease -eq $false -and $_.assets -and $_.assets.Count -gt 0 } | Select-Object -First 1
if ($release -and $release.tag_name) {
$tagName = $release.tag_name
}
} catch { } catch {
Write-Err "GitHub API call failed: $_" Write-Warn "GitHub API call failed: $_. Using fallback tag: $FALLBACK_TAG"
} }
$tagName = $release.tag_name
$version = $tagName -replace '^v', '' $version = $tagName -replace '^v', ''
Write-Info "Latest Helix version confirmed: $tagName" Write-Info "Target Helix version: $tagName"
# 3) 윈도우 x86_64용 zip 파일 에셋 탐색 # 3) 윈도우 x86_64용 zip 파일 에셋 탐색
$asset = $null
if ($release -and $release.assets) {
$asset = $release.assets | Where-Object { $_.name -like "*-x86_64-windows.zip" } | Select-Object -First 1 $asset = $release.assets | Where-Object { $_.name -like "*-x86_64-windows.zip" } | Select-Object -First 1
}
if (-not $asset) { if (-not $asset) {
# 에셋 조회가 되지 않을 경우 릴리스 패턴에 맞추어 다운로드 URL 직접 빌드
$zipName = "helix-${version}-x86_64-windows.zip" $zipName = "helix-${version}-x86_64-windows.zip"
$downloadUrl = "https://github.com/$REPO/releases/download/$tagName/$zipName" $downloadUrl = "https://github.com/$REPO/releases/download/$tagName/$zipName"
Write-Warn "Could not find asset in GitHub response. Trying fallback URL: $downloadUrl" Write-Info "Resolved URL: $downloadUrl"
} else { } else {
$zipName = $asset.name $zipName = $asset.name
$downloadUrl = $asset.browser_download_url $downloadUrl = $asset.browser_download_url
} }
$fallbackVersion = $FALLBACK_TAG -replace '^v', ''
$fallbackZipName = "helix-${fallbackVersion}-x86_64-windows.zip"
$fallbackUrl = "https://github.com/$REPO/releases/download/$FALLBACK_TAG/$fallbackZipName"
# 4) 임시 작업 디렉토리 생성 및 아카이브 다운로드 # 4) 임시 작업 디렉토리 생성 및 아카이브 다운로드
$tmpDir = Join-Path $env:TEMP "helix_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "helix_install_$(Get-Random)"
@@ -105,8 +116,17 @@ function Main {
try { try {
Write-Info "Downloading archive: $downloadUrl" Write-Info "Downloading archive: $downloadUrl"
$ProgressPreference = 'SilentlyContinue' # 다운로드 속도 향상을 위해 진행바 숨김 $ProgressPreference = 'SilentlyContinue'
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $tmpZipPath -UseBasicParsing Invoke-WebRequest -Uri $downloadUrl -OutFile $tmpZipPath -UseBasicParsing
} catch {
if ($downloadUrl -ne $fallbackUrl) {
Write-Warn "Primary download failed. Retrying with fallback version ($FALLBACK_TAG): $fallbackUrl"
Invoke-WebRequest -Uri $fallbackUrl -OutFile $tmpZipPath -UseBasicParsing
} else {
throw
}
}
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
# 5) 압축 해제 # 5) 압축 해제
+33 -10
View File
@@ -14,7 +14,8 @@ set -euo pipefail
# 상수 정의 # 상수 정의
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="helix-editor/helix" REPO="helix-editor/helix"
API_URL="https://api.github.com/repos/${REPO}/releases/latest" API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="25.07.1"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
HELIX_CONFIG_DIR="${HOME}/.config/helix" HELIX_CONFIG_DIR="${HOME}/.config/helix"
@@ -33,20 +34,26 @@ for cmd in curl tar xz uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다. 먼저 설치해 주세요." command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다. 먼저 설치해 주세요."
done done
# Fallback: jq가 없을 경우 python3를 사용해 JSON 분석 # Fallback: jq가 없을 경우 python3 또는 grep을 사용해 JSON 분석
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_latest_tag() { _json_latest_tag() {
jq -r '.tag_name' jq -r '[.[] | select(.prerelease==false and ((.assets | length) > 0))] | .[0].tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' 또는 'python3' 명령어가 설치되어 있어야 합니다."
_json_latest_tag() { _json_latest_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
print(data.get('tag_name', '')) for r in data:
if not r.get('prerelease') and r.get('assets'):
print(r.get('tag_name', ''))
break
" "
} }
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -93,12 +100,15 @@ detect_platform() {
get_latest_release_tag() { get_latest_release_tag() {
info "GitHub API를 통해 Helix 최신 릴리스 버전을 조회하는 중..." info "GitHub API를 통해 Helix 최신 릴리스 버전을 조회하는 중..."
local release_json tag local release_json="" tag=""
release_json="$(curl -fsSL "${API_URL}")" || error "GitHub API 호출에 실패했습니다." release_json="$(curl -fsSL -H "User-Agent: install-helix-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
tag="$(echo "$release_json" | _json_latest_tag)" if [[ -n "$release_json" ]]; then
tag="$(echo "$release_json" | _json_latest_tag 2>/dev/null || true)"
fi
if [[ -z "$tag" || "$tag" == "null" ]]; then if [[ -z "$tag" || "$tag" == "null" ]]; then
error "최신 릴리스 태그를 확인하는 데 실패했습니다." warn "최신 릴리스 태그 조회 실패. 검증된 폴백 버전(${FALLBACK_TAG})을 사용합니다."
tag="${FALLBACK_TAG}"
fi fi
echo "$tag" echo "$tag"
} }
@@ -126,10 +136,23 @@ main() {
local tmp_archive="${tmp_dir}/${archive_name}" local tmp_archive="${tmp_dir}/${archive_name}"
info "아카이브 다운로드 중: $download_url" info "아카이브 다운로드 중: $download_url"
if ! curl -fsSL "$download_url" -o "$tmp_archive"; then
if [[ "$tag" != "$FALLBACK_TAG" ]]; then
tag="${FALLBACK_TAG}"
version="${tag#v}"
asset_name="helix-${version}-${platform_arch}"
archive_name="${asset_name}.tar.xz"
download_url="https://github.com/helix-editor/helix/releases/download/${tag}/${archive_name}"
warn "다운로드 실패. 폴백 버전(${FALLBACK_TAG})으로 재시도합니다: $download_url"
if ! curl -fsSL "$download_url" -o "$tmp_archive"; then if ! curl -fsSL "$download_url" -o "$tmp_archive"; then
rm -rf "$tmp_dir" rm -rf "$tmp_dir"
error "바이너리 다운로드에 실패했습니다. URL: $download_url" error "바이너리 다운로드에 실패했습니다. URL: $download_url"
fi fi
else
rm -rf "$tmp_dir"
error "바이너리 다운로드에 실패했습니다. URL: $download_url"
fi
fi
info "압축 해제 중..." info "압축 해제 중..."
tar -xf "$tmp_archive" -C "$tmp_dir" tar -xf "$tmp_archive" -C "$tmp_dir"
+27 -11
View File
@@ -48,6 +48,7 @@ $ErrorActionPreference = 'Stop'
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "jesseduffield/lazydocker" $REPO = "jesseduffield/lazydocker"
$API_URL = "https://api.github.com/repos/$REPO/releases" $API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "v0.25.2"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
# Utility functions # Utility functions
@@ -96,18 +97,21 @@ function Get-LatestTag {
try { try {
$releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing `
-Headers @{ 'User-Agent' = 'install-lazydocker-ps1' } -Headers @{ 'User-Agent' = 'install-lazydocker-ps1' } -TimeoutSec 15
} catch {
Write-Err "Failed to call GitHub API: $_"
}
$latest = $releases | Where-Object { $_.prerelease -eq $false } | Select-Object -First 1 $latest = $releases | Where-Object {
$_.prerelease -eq $false -and $_.assets -and $_.assets.Count -gt 0
if (-not $latest) { } | Select-Object -First 1
Write-Err "Could not find latest release."
}
if ($latest -and $latest.tag_name) {
return $latest.tag_name return $latest.tag_name
}
} catch {
Write-Warn "GitHub API lookup failed: $_"
}
Write-Warn "Using fallback release tag: $FALLBACK_TAG"
return $FALLBACK_TAG
} }
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -124,7 +128,7 @@ function Get-DownloadUrl {
# Download, extract & install # Download, extract & install
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Install-Lazydocker { function Install-Lazydocker {
param([string]$Url) param([string]$Url, [string]$FallbackUrl)
$tmpDir = Join-Path $env:TEMP "lazydocker_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "lazydocker_install_$(Get-Random)"
New-Item -ItemType Directory -Path $tmpDir | Out-Null New-Item -ItemType Directory -Path $tmpDir | Out-Null
@@ -137,9 +141,20 @@ function Install-Lazydocker {
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
} catch {
if ($FallbackUrl -and ($FallbackUrl -ne $Url)) {
Write-Warn "Primary download failed. Retrying with fallback URL: $FallbackUrl"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $FallbackUrl -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch { } catch {
Write-Err "Download failed: $_" Write-Err "Download failed: $_"
} }
} else {
Write-Err "Download failed: $_"
}
}
Write-Info "Extracting..." Write-Info "Extracting..."
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
@@ -223,7 +238,8 @@ function Main {
Write-Info "Latest release: $tag" Write-Info "Latest release: $tag"
$url = Get-DownloadUrl -Tag $tag -Platform $platform $url = Get-DownloadUrl -Tag $tag -Platform $platform
Install-Lazydocker -Url $url $fallbackUrl = Get-DownloadUrl -Tag $FALLBACK_TAG -Platform $platform
Install-Lazydocker -Url $url -FallbackUrl $fallbackUrl
if ($AddToPath) { if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir Add-ToUserPath -Dir $InstallDir
+30 -12
View File
@@ -16,6 +16,7 @@ set -euo pipefail
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="jesseduffield/lazydocker" REPO="jesseduffield/lazydocker"
API_URL="https://api.github.com/repos/${REPO}/releases" API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="v0.25.2"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -55,23 +56,26 @@ for cmd in curl uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' command is required." command -v "$cmd" &>/dev/null || error "'$cmd' command is required."
done done
# Fallback: use python3 if jq is not available # Fallback: use python3 or grep if jq is not available
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_latest_tag() { _json_latest_tag() {
jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name' jq -r '[.[] | select(.prerelease==false and ((.assets | length) > 0))] | .[0].tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' or 'python3' is required."
_json_latest_tag() { _json_latest_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
for r in data: for r in data:
if not r.get('prerelease'): if not r.get('prerelease') and r.get('assets'):
print(r['tag_name']) print(r['tag_name'])
break break
" "
} }
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -124,13 +128,17 @@ detect_platform() {
get_latest_tag() { get_latest_tag() {
info "Fetching latest release from GitHub API..." info "Fetching latest release from GitHub API..."
local releases local releases="" tag=""
releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." releases="$(curl -fsSL -H "User-Agent: install-lazydocker-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
local tag if [[ -n "$releases" ]]; then
tag="$(echo "$releases" | _json_latest_tag)" tag="$(echo "$releases" | _json_latest_tag 2>/dev/null || true)"
fi
[[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag." if [[ -z "$tag" || "$tag" == "null" ]]; then
warn "Could not fetch latest release tag from GitHub API. Using fallback tag: ${FALLBACK_TAG}"
tag="${FALLBACK_TAG}"
fi
echo "$tag" echo "$tag"
} }
@@ -153,6 +161,7 @@ build_download_url() {
download_and_install() { download_and_install() {
local url="$1" local url="$1"
local ext="$2" local ext="$2"
local fallback_url="${3:-}"
local tmpdir local tmpdir
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -162,8 +171,15 @@ download_and_install() {
filename="${url##*/}" filename="${url##*/}"
info "Downloading: $url" info "Downloading: $url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" || if ! curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url"; then
if [[ -n "$fallback_url" && "$fallback_url" != "$url" ]]; then
warn "Primary download failed. Retrying with fallback URL: $fallback_url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$fallback_url" ||
error "Download failed: $fallback_url"
else
error "Download failed: $url" error "Download failed: $url"
fi
fi
info "Extracting..." info "Extracting..."
case "$ext" in case "$ext" in
@@ -248,7 +264,9 @@ main() {
info "Latest release: $tag" info "Latest release: $tag"
url="$(build_download_url "$tag" "$platform" "$ext")" url="$(build_download_url "$tag" "$platform" "$ext")"
download_and_install "$url" "$ext" local fallback_url
fallback_url="$(build_download_url "$FALLBACK_TAG" "$platform" "$ext")"
download_and_install "$url" "$ext" "$fallback_url"
fi fi
fi fi
+27 -11
View File
@@ -48,6 +48,7 @@ $ErrorActionPreference = 'Stop'
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "jesseduffield/lazygit" $REPO = "jesseduffield/lazygit"
$API_URL = "https://api.github.com/repos/$REPO/releases" $API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "v0.61.1"
$GIT_REPO = "git-for-windows/git" $GIT_REPO = "git-for-windows/git"
$GIT_API_URL = "https://api.github.com/repos/$GIT_REPO/releases" $GIT_API_URL = "https://api.github.com/repos/$GIT_REPO/releases"
@@ -254,18 +255,21 @@ function Get-LatestTag {
try { try {
$releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing `
-Headers @{ 'User-Agent' = 'install-lazygit-ps1' } -Headers @{ 'User-Agent' = 'install-lazygit-ps1' } -TimeoutSec 15
} catch {
Write-Err "Failed to call GitHub API: $_"
}
$latest = $releases | Where-Object { $_.prerelease -eq $false } | Select-Object -First 1 $latest = $releases | Where-Object {
$_.prerelease -eq $false -and $_.assets -and $_.assets.Count -gt 0
if (-not $latest) { } | Select-Object -First 1
Write-Err "Could not find latest release."
}
if ($latest -and $latest.tag_name) {
return $latest.tag_name return $latest.tag_name
}
} catch {
Write-Warn "GitHub API lookup failed: $_"
}
Write-Warn "Using fallback release tag: $FALLBACK_TAG"
return $FALLBACK_TAG
} }
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -282,7 +286,7 @@ function Get-DownloadUrl {
# Download, extract & install # Download, extract & install
# ──────────────────────────────────────────── # ────────────────────────────────────────────
function Install-Lazygit { function Install-Lazygit {
param([string]$Url) param([string]$Url, [string]$FallbackUrl)
$tmpDir = Join-Path $env:TEMP "lazygit_install_$(Get-Random)" $tmpDir = Join-Path $env:TEMP "lazygit_install_$(Get-Random)"
New-Item -ItemType Directory -Path $tmpDir | Out-Null New-Item -ItemType Directory -Path $tmpDir | Out-Null
@@ -295,9 +299,20 @@ function Install-Lazygit {
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
} catch {
if ($FallbackUrl -and ($FallbackUrl -ne $Url)) {
Write-Warn "Primary download failed. Retrying with fallback URL: $FallbackUrl"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $FallbackUrl -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch { } catch {
Write-Err "Download failed: $_" Write-Err "Download failed: $_"
} }
} else {
Write-Err "Download failed: $_"
}
}
Write-Info "Extracting..." Write-Info "Extracting..."
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
@@ -370,7 +385,8 @@ function Main {
Write-Info "Latest release: $tag" Write-Info "Latest release: $tag"
$url = Get-DownloadUrl -Tag $tag -Platform $platform $url = Get-DownloadUrl -Tag $tag -Platform $platform
Install-Lazygit -Url $url $fallbackUrl = Get-DownloadUrl -Tag $FALLBACK_TAG -Platform $platform
Install-Lazygit -Url $url -FallbackUrl $fallbackUrl
if ($AddToPath) { if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir Add-ToUserPath -Dir $InstallDir
+30 -17
View File
@@ -17,6 +17,7 @@ set -euo pipefail
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="jesseduffield/lazygit" REPO="jesseduffield/lazygit"
API_URL="https://api.github.com/repos/${REPO}/releases" API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="v0.61.1"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -48,11 +49,6 @@ ensure_brew() {
command -v brew &>/dev/null || error "Homebrew installation failed. Install it manually: https://brew.sh/" command -v brew &>/dev/null || error "Homebrew installation failed. Install it manually: https://brew.sh/"
success "Homebrew installed" success "Homebrew installed"
} }
error() {
echo "[ERROR] $*" >&2
exit 1
}
success() { echo "[OK] $*" >&2; }
# ──────────────────────────────────────────── # ────────────────────────────────────────────
# Check for git (required by lazygit) # Check for git (required by lazygit)
@@ -105,23 +101,26 @@ for cmd in curl uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' command is required." command -v "$cmd" &>/dev/null || error "'$cmd' command is required."
done done
# Fallback: use python3 if jq is not available # Fallback: use python3 or grep if jq is not available
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_latest_tag() { _json_latest_tag() {
jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name' jq -r '[.[] | select(.prerelease==false and ((.assets | length) > 0))] | .[0].tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' or 'python3' is required."
_json_latest_tag() { _json_latest_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
for r in data: for r in data:
if not r.get('prerelease'): if not r.get('prerelease') and r.get('assets'):
print(r['tag_name']) print(r['tag_name'])
break break
" "
} }
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -205,13 +204,17 @@ detect_platform() {
get_latest_tag() { get_latest_tag() {
info "Fetching latest release from GitHub API..." info "Fetching latest release from GitHub API..."
local releases local releases="" tag=""
releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." releases="$(curl -fsSL -H "User-Agent: install-lazygit-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
local tag if [[ -n "$releases" ]]; then
tag="$(echo "$releases" | _json_latest_tag)" tag="$(echo "$releases" | _json_latest_tag 2>/dev/null || true)"
fi
[[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag." if [[ -z "$tag" || "$tag" == "null" ]]; then
warn "Could not fetch latest release tag from GitHub API. Using fallback tag: ${FALLBACK_TAG}"
tag="${FALLBACK_TAG}"
fi
echo "$tag" echo "$tag"
} }
@@ -234,6 +237,7 @@ download_and_install() {
local url="$1" local url="$1"
local ext="$2" local ext="$2"
local is_windows="${3:-false}" local is_windows="${3:-false}"
local fallback_url="${4:-}"
local tmpdir local tmpdir
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -243,8 +247,15 @@ download_and_install() {
filename="${url##*/}" filename="${url##*/}"
info "Downloading: $url" info "Downloading: $url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" || if ! curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url"; then
if [[ -n "$fallback_url" && "$fallback_url" != "$url" ]]; then
warn "Primary download failed. Retrying with fallback URL: $fallback_url"
curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$fallback_url" ||
error "Download failed: $fallback_url"
else
error "Download failed: $url" error "Download failed: $url"
fi
fi
info "Extracting..." info "Extracting..."
case "$ext" in case "$ext" in
@@ -313,7 +324,9 @@ main() {
info "Latest release: $tag" info "Latest release: $tag"
url="$(build_download_url "$tag" "$platform" "$ext")" url="$(build_download_url "$tag" "$platform" "$ext")"
download_and_install "$url" "$ext" "$is_windows" local fallback_url
fallback_url="$(build_download_url "$FALLBACK_TAG" "$platform" "$ext")"
download_and_install "$url" "$ext" "$is_windows" "$fallback_url"
} }
main "$@" main "$@"
+13 -2
View File
@@ -57,8 +57,10 @@ if ($arch -eq "AMD64") {
$tag = $null $tag = $null
try { try {
Info "Fetching latest release tag from GitHub API..." Info "Fetching latest release tag from GitHub API..."
$response = Invoke-RestMethod -Uri $API_URL -TimeoutSec 10 $response = Invoke-RestMethod -Uri $API_URL -Headers @{ 'User-Agent' = 'install-ocr-ps1' } -TimeoutSec 10
if ($response -and $response.assets -and $response.assets.Count -gt 0) {
$tag = $response.tag_name $tag = $response.tag_name
}
} catch { } catch {
Warn "Failed to retrieve the latest version from GitHub API. Using fallback version: $FALLBACK_VERSION" Warn "Failed to retrieve the latest version from GitHub API. Using fallback version: $FALLBACK_VERSION"
$tag = $FALLBACK_VERSION $tag = $FALLBACK_VERSION
@@ -69,6 +71,7 @@ if (-not $tag) {
} }
$downloadUrl = "https://github.com/$REPO/releases/download/$tag/$assetName" $downloadUrl = "https://github.com/$REPO/releases/download/$tag/$assetName"
$fallbackUrl = "https://github.com/$REPO/releases/download/$FALLBACK_VERSION/$assetName"
Info "Resolved download URL: $downloadUrl" Info "Resolved download URL: $downloadUrl"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -85,8 +88,16 @@ $destFile = Join-Path $INSTALL_DIR "ocr.exe"
try { try {
Info "Downloading binary..." Info "Downloading binary..."
# Invoke-WebRequest를 사용해 다운로드 진행 try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempFile -UseBasicParsing Invoke-WebRequest -Uri $downloadUrl -OutFile $tempFile -UseBasicParsing
} catch {
if ($downloadUrl -ne $fallbackUrl) {
Warn "Primary download failed. Retrying with fallback version ($FALLBACK_VERSION): $fallbackUrl"
Invoke-WebRequest -Uri $fallbackUrl -OutFile $tempFile -UseBasicParsing
} else {
throw
}
}
# 다운로드 완료 후 복사 # 다운로드 완료 후 복사
if (Test-Path $destFile) { if (Test-Path $destFile) {
+9 -3
View File
@@ -71,9 +71,9 @@ get_latest_tag() {
local json tag local json tag
info "GitHub API에서 Open Code Review 최신 릴리즈 정보를 가져오는 중..." info "GitHub API에서 Open Code Review 최신 릴리즈 정보를 가져오는 중..."
if json=$(curl -fsSL --connect-timeout 10 "${API_URL}" 2>/dev/null); then if json=$(curl -fsSL -H "User-Agent: install-ocr-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null); then
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
tag=$(echo "$json" | jq -r '.tag_name') tag=$(echo "$json" | jq -r 'select((.assets | length) > 0) | .tag_name')
else else
# jq가 없는 경우 grep & sed 사용 # jq가 없는 경우 grep & sed 사용
tag=$(echo "$json" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/') tag=$(echo "$json" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')
@@ -92,7 +92,7 @@ get_latest_tag() {
# 메인 설치 함수 # 메인 설치 함수
# ──────────────────────────────────────────── # ────────────────────────────────────────────
main() { main() {
local asset_name tag url tmpdir dest_file local asset_name tag url fallback_url tmpdir dest_file
asset_name="$(detect_platform)" asset_name="$(detect_platform)"
info "감지된 플랫폼 에셋: $asset_name" info "감지된 플랫폼 에셋: $asset_name"
@@ -101,6 +101,7 @@ main() {
info "적용할 버전 태그: $tag" info "적용할 버전 태그: $tag"
url="https://github.com/${REPO}/releases/download/${tag}/${asset_name}" url="https://github.com/${REPO}/releases/download/${tag}/${asset_name}"
fallback_url="https://github.com/${REPO}/releases/download/${FALLBACK_VERSION}/${asset_name}"
# 임시 디렉토리 생성 # 임시 디렉토리 생성
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
@@ -110,8 +111,13 @@ main() {
info "바이너리 다운로드 중: $url" info "바이너리 다운로드 중: $url"
if ! curl -fsSL --progress-bar -o "$dest_file" "$url"; then if ! curl -fsSL --progress-bar -o "$dest_file" "$url"; then
if [[ "$tag" != "$FALLBACK_VERSION" ]]; then
warn "최신 버전(${tag}) 다운로드 실패. 검증된 폴백 버전(${FALLBACK_VERSION})으로 재시도합니다: $fallback_url"
curl -fsSL --progress-bar -o "$dest_file" "$fallback_url" || error "다운로드 실패: $fallback_url"
else
error "다운로드 실패: $url" error "다운로드 실패: $url"
fi fi
fi
# 설치 디렉토리 확보 # 설치 디렉토리 확보
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
+28 -10
View File
@@ -52,6 +52,7 @@ function Write-Err { param([string]$msg) Write-Host "[ERROR] $msg" -Foregrou
# ──────────────────────────────────────────── # ────────────────────────────────────────────
$REPO = "tsl0922/ttyd" $REPO = "tsl0922/ttyd"
$API_URL = "https://api.github.com/repos/$REPO/releases/latest" $API_URL = "https://api.github.com/repos/$REPO/releases/latest"
$FALLBACK_TAG = "1.7.7"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
# 메인 설치 프로세스 # 메인 설치 프로세스
@@ -59,24 +60,32 @@ $API_URL = "https://api.github.com/repos/$REPO/releases/latest"
# 1) GitHub 최신 릴리스 정보 획득 # 1) GitHub 최신 릴리스 정보 획득
Write-Info "Fetching latest ttyd release information from GitHub API..." Write-Info "Fetching latest ttyd release information from GitHub API..."
$release = $null
$tagName = $FALLBACK_TAG
try { try {
$release = Invoke-RestMethod -Uri $API_URL -UseBasicParsing -Headers @{ 'User-Agent' = 'install-ttyd-ps1' } $release = Invoke-RestMethod -Uri $API_URL -UseBasicParsing -Headers @{ 'User-Agent' = 'install-ttyd-ps1' } -TimeoutSec 15
if ($release -and $release.tag_name -and $release.assets -and $release.assets.Count -gt 0) {
$tagName = $release.tag_name
}
} catch { } catch {
Write-Err "GitHub API call failed: $_" Write-Warn "GitHub API call failed: $_. Using fallback tag: $FALLBACK_TAG"
} }
$tagName = $release.tag_name Write-Info "Target ttyd version: $tagName"
Write-Info "Latest ttyd version confirmed: $tagName"
# 2) ttyd.win32.exe 에셋 탐색 # 2) ttyd.win32.exe 에셋 탐색
$assetName = "ttyd.win32.exe" $assetName = "ttyd.win32.exe"
$asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1 $asset = $null
if ($release -and $release.assets) {
if (-not $asset) { $asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
Write-Err "Could not find '$assetName' binary in release $tagName."
} }
$downloadUrl = $asset.browser_download_url if ($asset -and $asset.browser_download_url) {
$downloadUrl = $asset.browser_download_url
} else {
$downloadUrl = "https://github.com/$REPO/releases/download/$tagName/$assetName"
}
$fallbackUrl = "https://github.com/$REPO/releases/download/$FALLBACK_TAG/$assetName"
$destExeName = "ttyd.exe" $destExeName = "ttyd.exe"
# 3) 다운로드 및 디렉토리 생성 # 3) 다운로드 및 디렉토리 생성
@@ -92,8 +101,17 @@ $tmpFilePath = Join-Path $tmpDir $assetName
try { try {
Write-Info "Downloading binary: $downloadUrl" Write-Info "Downloading binary: $downloadUrl"
$ProgressPreference = 'SilentlyContinue' # 다운로드 진행 바 숨김으로 속도 향상 $ProgressPreference = 'SilentlyContinue'
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $tmpFilePath -UseBasicParsing Invoke-WebRequest -Uri $downloadUrl -OutFile $tmpFilePath -UseBasicParsing
} catch {
if ($downloadUrl -ne $fallbackUrl) {
Write-Warn "Primary download failed. Retrying with fallback version ($FALLBACK_TAG): $fallbackUrl"
Invoke-WebRequest -Uri $fallbackUrl -OutFile $tmpFilePath -UseBasicParsing
} else {
throw
}
}
$ProgressPreference = 'Continue' $ProgressPreference = 'Continue'
Write-Info "Deploying binary to install path: $destPath" Write-Info "Deploying binary to install path: $destPath"
+27 -10
View File
@@ -17,6 +17,7 @@ set -euo pipefail
# ──────────────────────────────────────────── # ────────────────────────────────────────────
REPO="tsl0922/ttyd" REPO="tsl0922/ttyd"
API_URL="https://api.github.com/repos/${REPO}/releases/latest" API_URL="https://api.github.com/repos/${REPO}/releases/latest"
FALLBACK_TAG="1.7.7"
INSTALL_DIR="${HOME}/.local/bin" INSTALL_DIR="${HOME}/.local/bin"
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -53,20 +54,24 @@ for cmd in curl uname; do
command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다." command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다."
done done
# Fallback: jq가 없을 경우 python3를 사용해 JSON 정보 분석 # Fallback: jq가 없을 경우 python3 또는 grep을 사용해 JSON 정보 분석
if command -v jq &>/dev/null; then if command -v jq &>/dev/null; then
_json_latest_tag() { _json_latest_tag() {
jq -r '.tag_name' jq -r 'select((.assets | length) > 0) | .tag_name'
} }
else elif command -v python3 &>/dev/null; then
command -v python3 &>/dev/null || error "'jq' 또는 'python3' 명령어가 설치되어 있어야 합니다."
_json_latest_tag() { _json_latest_tag() {
python3 -c " python3 -c "
import sys, json import sys, json
data = json.load(sys.stdin) data = json.load(sys.stdin)
print(data.get('tag_name', '')) if data.get('assets'):
print(data.get('tag_name', ''))
" "
} }
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/'
}
fi fi
# ──────────────────────────────────────────── # ────────────────────────────────────────────
@@ -121,12 +126,15 @@ detect_platform() {
get_latest_release_tag() { get_latest_release_tag() {
info "GitHub API를 통해 ttyd 최신 릴리스 버전을 가져오는 중..." info "GitHub API를 통해 ttyd 최신 릴리스 버전을 가져오는 중..."
local release_json tag local release_json="" tag=""
release_json="$(curl -fsSL "${API_URL}")" || error "GitHub API 호출에 실패했습니다." release_json="$(curl -fsSL -H "User-Agent: install-ttyd-sh" --connect-timeout 10 "${API_URL}" 2>/dev/null)" || true
tag="$(echo "$release_json" | _json_latest_tag)" if [[ -n "$release_json" ]]; then
tag="$(echo "$release_json" | _json_latest_tag 2>/dev/null || true)"
fi
if [[ -z "$tag" || "$tag" == "null" ]]; then if [[ -z "$tag" || "$tag" == "null" ]]; then
error "최신 릴리스 태그를 확인하는 데 실패했습니다." warn "최신 릴리스 태그 조회 실패. 검증된 폴백 버전(${FALLBACK_TAG})을 사용합니다."
tag="${FALLBACK_TAG}"
fi fi
echo "$tag" echo "$tag"
} }
@@ -156,11 +164,12 @@ main() {
fi fi
# 2) Linux / Windows Bash (바이너리 직접 설치 방식) # 2) Linux / Windows Bash (바이너리 직접 설치 방식)
local tag download_url local tag download_url fallback_url
tag="$(get_latest_release_tag)" tag="$(get_latest_release_tag)"
info "최신 버전 감지: $tag" info "최신 버전 감지: $tag"
download_url="https://github.com/tsl0922/ttyd/releases/download/${tag}/${asset_name}" download_url="https://github.com/tsl0922/ttyd/releases/download/${tag}/${asset_name}"
fallback_url="https://github.com/tsl0922/ttyd/releases/download/${FALLBACK_TAG}/${asset_name}"
# 설치 디렉토리 생성 # 설치 디렉토리 생성
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
@@ -176,9 +185,17 @@ main() {
info "바이너리 다운로드 중: $download_url" info "바이너리 다운로드 중: $download_url"
if ! curl -fsSL "$download_url" -o "$tmp_file"; then if ! curl -fsSL "$download_url" -o "$tmp_file"; then
if [[ "$tag" != "$FALLBACK_TAG" ]]; then
warn "다운로드 실패. 폴백 버전(${FALLBACK_TAG})으로 재시도합니다: $fallback_url"
if ! curl -fsSL "$fallback_url" -o "$tmp_file"; then
rm -rf "$tmp_dir"
error "바이너리 다운로드에 실패했습니다. URL: $fallback_url"
fi
else
rm -rf "$tmp_dir" rm -rf "$tmp_dir"
error "바이너리 다운로드에 실패했습니다. URL: $download_url" error "바이너리 다운로드에 실패했습니다. URL: $download_url"
fi fi
fi
info "설치 경로로 복사 중: $dest_file" info "설치 경로로 복사 중: $dest_file"
mv "$tmp_file" "$dest_file" mv "$tmp_file" "$dest_file"
Executable
+7
View File
@@ -0,0 +1,7 @@
# 2초마다 0번 팬(install-sh:0.0) 화면을 확인하여 선택 프롬프트가 뜨면 Enter(기본값 1번 선택) 전송
while true; do
if tmux capture-pane -t install-sh:0.0 -p | grep -q "> 1\. Yes"; then
tmux send-keys -t install-sh:0.0 Enter
fi
sleep 2
done