dua 설치 스크립트 최신 릴리스 태그 필터링 오류 수정

This commit is contained in:
mcchae committed 2026-10-31 04:28:07 +09:00
1 parent 8f2177b3b5
commit 5a67e7bbcf
4 files changed
+331 -174

No files matched your search

@@ -0,0 +1,38 @@
# dua 설치 스크립트 오류 수정 계획 (FIX-DUA-INSTALL-IMP-PLAN)
## 1. 원인 분석
`Byron/dua-cli` GitHub 저장소는 워크스페이스 내 라이브러리 크레이트(`dua-core-v5.0.1`, `dua-core-v5.0.0` 등)의 릴리스도 함께 발행합니다.
기존 `install-dua.sh`는 단순히 `prerelease == false`인 첫 번째 릴리스 태그를 가져오도록 작성되어 있어, 바이너리 에셋이 전혀 없는 `dua-core-v5.0.1` 태그를 선택하고 `dua-dua-core-v5.0.1-x86_64-unknown-linux-musl.tar.gz`와 같은 존재하지 않는 다운로드 URL을 요청하여 404 에러로 실패하였습니다.
또한 Windows용 `install-dua.ps1`은 `winget`에만 의존하고 있어, GitHub Releases에서 직접 최신 `v*` 바이너리를 다운로드하거나 폴백으로 설치하는 기능이 부족했습니다.
```mermaid
flowchart TD
Start([설치 시작]) --> API[GitHub Releases API 조회]
API --> Filter{태그가 'v*' 로 시작하고<br/>assets가 존재하는가?}
Filter -- No (예: dua-core-v*) --> Skip[무시하고 다음 릴리스 검사]
Skip --> Filter
Filter -- Yes (예: v2.46.0) --> Download[아키텍처별 에셋 다운로드<br/>dua-vX.Y.Z-target.tar.gz/.zip]
Download --> Extract[압축 해제 및 ~/.local/bin 배포]
Extract --> PathConfig[PATH 환경변수 등록]
PathConfig --> Done([설치 완료])
```
## 2. 작업 단계 및 체크리스트
### 단계 1: 원인 분석 및 계획 수립
- [x] GitHub Releases API 응답 구조 확인 (`dua-core-v*` vs `v*`)
- [x] 실행 계획서(`VIBE-WORK/261031/FIX-DUA-INSTALL-IMP-PLAN.md`) 작성
### 단계 2: `install-dua.sh` 수정
- [x] `_json_latest_tag` 함수에서 `prerelease == false` 조건뿐만 아니라 `tag_name`이 `v`로 시작하고(`startswith("v")`) `assets` 배열이 비어있지 않은 릴리스만 선택하도록 필터 강화
- [x] GitHub API 호출 시 User-Agent 헤더 및 Fallback 태그(`v2.46.0`) 안전장치 추가
- [x] `bash -n install-dua.sh` 문법 검사 및 태그 추출 테스트
### 단계 3: `install-dua.ps1` 개선
- [x] GitHub Releases에서 `v*` 태그의 Windows 바이너리(`dua-{TAG}-{ARCH}-pc-windows-msvc.zip`)를 직접 다운로드하여 `$env:USERPROFILE\.local\bin`에 설치하도록 개선 (다른 스크립트들과 일관된 방식)
- [x] PATH 환경변수 자동 등록 및 버전 확인 로직 구성
### 단계 4: 완료 보고서 작성 및 Graphify 업데이트 / Git 푸시
- [x] 완료 보고서(`VIBE-WORK/261031/FIX-DUA-INSTALL-PHASE01.md`) 작성
- [x] `uv tool run --from graphifyy graphify update .` 실행
- [x] Git commit 및 push
@@ -0,0 +1,20 @@
# dua 설치 스크립트 오류 수정 완료 보고서 (FIX-DUA-INSTALL-PHASE01)
## 1. 장애 원인
`Byron/dua-cli` GitHub 저장소에서 CLI 바이너리 릴리스(`v2.46.0` 등) 외에 내부 코어 라이브러리 릴리스(`dua-core-v5.0.1` 등, 바이너리 에셋 없음)를 함께 발행하기 시작하면서, 기존 `prerelease == false` 조건만 검사하던 로직이 `dua-core-v5.0.1` 태그를 선택하여 존재하지 않는 URL(`.../dua-dua-core-v5.0.1-...`)을 다운로드하려다 404 오류가 발생하였습니다.
```mermaid
flowchart LR
API[GitHub Releases API] --> Old[기존: 첫 번째 prerelease=false 선택<br/>dua-core-v5.0.1 선택됨 -> 404 에러]
API --> New[수정: tag_name이 'v*'로 시작하고<br/>assets가 존재하는 릴리스만 선택<br/>v2.46.0 정상 다운로드]
```
## 2. 주요 변경 사항
### 2.1. `install-dua.sh`
- `jq` 및 `python3`, `grep` 파서에서 `tag_name`이 `v`로 시작하고(`startswith("v")`) `assets` 배열이 비어있지 않은 릴리스만 필터링하도록 수정
- GitHub API Rate Limit 등으로 실패할 경우를 대비한 `FALLBACK_TAG="v2.46.0"` 안전장치 추가
- Linux에서 불필요하게 실패하는 배포판 패키지 매니저(`apt`, `dnf` 등에는 기본적으로 `dua-cli`가 없음) 시도 과정을 제거하고 GitHub 공식 정적 바이너리(`musl`)를 `~/.local/bin/dua`에 직접 설치하도록 개선
### 2.2. `install-dua.ps1`
- `winget` 의존성을 제거하고 GitHub Releases API에서 `v*` 태그의 Windows 아키텍처별 바이너리(`dua-{TAG}-{ARCH}-pc-windows-msvc.zip`)를 직접 다운로드하여 `$env:USERPROFILE\.local\bin\dua.exe`에 설치하고 사용자 PATH에 자동 등록하도록 개선
+181 -82
View File
@@ -1,18 +1,24 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Installs dua (Disk Usage Analyzer).
Installs dua (Disk Usage Analyzer) from GitHub releases.
.DESCRIPTION
Installs dua via winget (Byron.dua-cli) on Windows.
dua is a fast disk usage analyzer with interactive TUI mode.
Auto-detects the latest CLI release (v*) from
https://github.com/Byron/dua-cli/releases, downloads the zip archive
matching your Windows architecture, and installs dua.exe into
$env:USERPROFILE\.local\bin.
Supported platforms:
- Windows x64 (AMD64 / Intel 64bit)
- Windows ARM64
- Windows x86 (32bit)
Prerequisites:
- winget (Windows Package Manager) — built into Windows 10 1709+
.PARAMETER InstallDir
Directory to install dua.exe. Default: $env:USERPROFILE\.local\bin
.PARAMETER AddToPath
Whether to permanently add InstallDir to user PATH. Default: $true
.EXAMPLE
.\install-dua.ps1
@@ -26,7 +32,10 @@
#>
[CmdletBinding()]
param()
param(
[string] $InstallDir = "$env:USERPROFILE\.local\bin",
[bool] $AddToPath = $true
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
@@ -34,6 +43,13 @@ $ErrorActionPreference = 'Stop'
# Force TLS 1.2+ for HTTPS downloads (required on Windows 10 / PowerShell 5.1)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# ────────────────────────────────────────────
# Constants
# ────────────────────────────────────────────
$REPO = "Byron/dua-cli"
$API_URL = "https://api.github.com/repos/$REPO/releases"
$FALLBACK_TAG = "v2.46.0"
# ────────────────────────────────────────────
# Utility functions
# ────────────────────────────────────────────
@@ -43,103 +59,183 @@ function Success { Write-Host "[OK] $args" -ForegroundColor Green }
function ErrExit { Write-Host "[ERROR] $args" -ForegroundColor Red; exit 1 }
# ────────────────────────────────────────────
# Step 1: Install dua
# Architecture detection
# ────────────────────────────────────────────
Info "Installing dua (Disk Usage Analyzer)..."
function Get-Platform {
$envArch = $env:PROCESSOR_ARCHITEW6432
if (-not $envArch) { $envArch = $env:PROCESSOR_ARCHITECTURE }
$duaCmd = Get-Command dua -ErrorAction SilentlyContinue
if ($duaCmd) {
$currentVer = & dua --version 2>$null
if (-not $currentVer) { $currentVer = "unknown" }
Info "dua is already installed ($currentVer). Skipping installation."
switch -Regex ($envArch) {
'^AMD64$' { return "x86_64-pc-windows-msvc" }
'^ARM64$' { return "aarch64-pc-windows-msvc" }
'^x86$' { return "i686-pc-windows-msvc" }
}
try {
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
switch ($arch) {
'X64' { return "x86_64-pc-windows-msvc" }
'Arm64' { return "aarch64-pc-windows-msvc" }
'X86' { return "i686-pc-windows-msvc" }
default { ErrExit "Unsupported architecture: $arch" }
}
} catch {
ErrExit "Failed to detect architecture: $_"
}
}
# ────────────────────────────────────────────
# Get latest stable CLI release tag (starts with 'v')
# ────────────────────────────────────────────
function Get-LatestTag {
Info "Fetching latest dua release from GitHub API..."
try {
$releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing `
-Headers @{ 'User-Agent' = 'install-dua-ps1' } -TimeoutSec 15
# Byron/dua-cli also publishes 'dua-core-v*' library tags without binary assets.
# Filter only CLI releases whose tag_name starts with 'v' and have assets.
$latest = $releases | Where-Object {
$_.prerelease -eq $false -and
$_.tag_name -like 'v*' -and
$_.assets -and
$_.assets.Count -gt 0
} | Select-Object -First 1
if ($latest -and $latest.tag_name) {
return $latest.tag_name
}
} catch {
Warn "GitHub API lookup failed: $_"
}
Warn "Using fallback release tag: $FALLBACK_TAG"
return $FALLBACK_TAG
}
# ────────────────────────────────────────────
# Build download URL
# ────────────────────────────────────────────
function Get-DownloadUrl {
param([string]$Tag, [string]$Triple)
$filename = "dua-${Tag}-${Triple}.zip"
return "https://github.com/$REPO/releases/download/$Tag/$filename"
}
# ────────────────────────────────────────────
# Download, extract & install
# ────────────────────────────────────────────
function Install-Dua {
param([string]$Url)
$tmpDir = Join-Path $env:TEMP "dua_install_$(Get-Random)"
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
try {
$zipPath = Join-Path $tmpDir "dua.zip"
Info "Downloading: $Url"
try {
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing
$ProgressPreference = 'Continue'
} catch {
ErrExit "Download failed from $Url : $_"
}
Info "Extracting archive..."
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
$duaExe = Get-ChildItem -Path $tmpDir -Recurse -Filter "dua.exe" |
Select-Object -First 1
if (-not $duaExe) {
ErrExit "Cannot find 'dua.exe' in the downloaded archive."
}
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
$destPath = Join-Path $InstallDir "dua.exe"
Info "Installing to: $destPath"
Copy-Item -Path $duaExe.FullName -Destination $destPath -Force
Success "dua installed to $destPath"
return $destPath
} finally {
if (Test-Path $tmpDir) {
Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
# ────────────────────────────────────────────
# Add to PATH (user environment variable)
# ────────────────────────────────────────────
function Add-ToUserPath {
param([string]$Dir)
$normalizedDir = $Dir.TrimEnd('\')
if ($env:Path -notlike "*$normalizedDir*") {
$env:Path = "$normalizedDir;$env:Path"
Info "Added $normalizedDir to current session PATH."
}
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not $currentPath) { $currentPath = '' }
$dirs = @($currentPath -split ';' | Where-Object { $_ -ne '' })
$alreadyInPath = $false
foreach ($d in $dirs) {
if ($d.Trim().TrimEnd('\') -eq $normalizedDir) {
$alreadyInPath = $true
break
}
}
if (-not $alreadyInPath) {
$newPath = if ($currentPath) { "$normalizedDir;$currentPath" } else { $normalizedDir }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Success "Added '$normalizedDir' to User PATH permanently."
Warn "Open a new terminal for PATH changes to take full effect."
} else {
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
ErrExit "winget is required. Install it from the Microsoft Store or https://learn.microsoft.com/en-us/windows/package-manager/winget/"
Info "'$normalizedDir' is already in User PATH."
}
winget install --id Byron.dua-cli --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
Success "dua installed via winget"
}
# ────────────────────────────────────────────
# Verify dua is available and add to PATH permanently if needed
# Main
# ────────────────────────────────────────────
$duaBin = $null
function Main {
Info "===== dua (Disk Usage Analyzer) install script (Windows PowerShell) ====="
# 1) Try current PATH
$duaCmd = Get-Command dua -ErrorAction SilentlyContinue
if ($duaCmd) {
$duaBin = $duaCmd.Source
}
$triple = Get-Platform
Info "Detected platform: $triple"
# 2) Refresh PATH from registry (winget may have added paths)
if (-not $duaBin) {
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'User') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'Machine')
$duaCmd = Get-Command dua -ErrorAction SilentlyContinue
if ($duaCmd) {
$duaBin = $duaCmd.Source
}
}
$tag = Get-LatestTag
Info "Release tag: $tag"
# 3) Search common locations where winget installs
if (-not $duaBin) {
$searchDirs = @(
(Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Links'),
(Join-Path $env:LOCALAPPDATA 'Programs\dua-cli'),
'C:\Program Files\dua-cli',
'C:\Program Files (x86)\dua-cli'
)
foreach ($dir in $searchDirs) {
if (Test-Path $dir) {
$candidate = Join-Path $dir 'dua.exe'
if (Test-Path $candidate) { $duaBin = $candidate; break }
}
}
$url = Get-DownloadUrl -Tag $tag -Triple $triple
$duaBin = Install-Dua -Url $url
# Recurse under WinGet/Packages for dua
if (-not $duaBin -and (Test-Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages'))) {
$found = Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') -Recurse -Filter 'dua.exe' -ErrorAction SilentlyContinue | Select-Object -First 1
if ($found) { $duaBin = $found.FullName }
}
}
if ($duaBin) {
# Add dua's directory to PATH for this session
$duaDir = Split-Path $duaBin
if ($env:Path -notlike "*$duaDir*") {
$env:Path = "$duaDir;$env:Path"
Info "Added $duaDir to PATH for this session"
}
Success "dua is available: $duaBin"
# Permanently add dua's directory to user PATH if not already there
$userPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$duaDir*") {
[System.Environment]::SetEnvironmentVariable('Path', "$duaDir;$userPath", 'User')
Success "Added $duaDir to user PATH permanently (takes effect on next login)"
if ($AddToPath) {
Add-ToUserPath -Dir $InstallDir
} else {
Info "$duaDir already in user PATH"
}
} else {
Warn "dua was not found in PATH after installation."
Warn "Please restart your terminal or add dua's directory to your PATH manually."
Warn "Skipping automatic PATH registration."
Warn "Add to PATH manually: $InstallDir"
}
# ────────────────────────────────────────────
# Step 2: Post-install notes
# ────────────────────────────────────────────
Info ""
Info "=================================================="
Success "dua installation complete!"
Info "=================================================="
if ($duaBin) {
Info ""
Info "dua binary: $duaBin"
if ($duaBin -and (Test-Path $duaBin)) {
$ver = & $duaBin --version 2>$null
Info "Installed version: $ver ($duaBin)"
}
Info ""
Info "IMPORTANT: Close this terminal and open a new one to use dua."
Info "PATH changes require a new terminal session to take effect."
Info ""
Info "Getting started:"
Info " dua - Show disk usage of current directory"
Info " dua /path/to/dir - Show disk usage of specific directory"
@@ -151,3 +247,6 @@ Info " Arrow keys / h/j/k/l - Navigate"
Info " ? - Show help"
Info " d - Delete selected entry"
Info " q / Esc - Quit"
}
Main
+92 -92
View File
@@ -18,6 +18,7 @@ set -euo pipefail
# ────────────────────────────────────────────
REPO="Byron/dua-cli"
API_URL="https://api.github.com/repos/${REPO}/releases"
FALLBACK_TAG="v2.46.0"
INSTALL_DIR="${HOME}/.local/bin"
# ────────────────────────────────────────────
@@ -71,22 +72,28 @@ for cmd in curl; do
done
# Fallback: use python3 if jq is not available
# Note: Byron/dua-cli publishes library tags like 'dua-core-v5.0.1' without binary assets.
# Filter only CLI releases whose tag starts with 'v' and contain downloadable assets.
if command -v jq &>/dev/null; then
_json_latest_tag() {
jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name'
jq -r '[.[] | select(.prerelease==false and (.tag_name | startswith("v")) and (.assets | length > 0))] | .[0].tag_name'
}
else
command -v python3 &>/dev/null || error "'jq' or 'python3' is required."
elif command -v python3 &>/dev/null; then
_json_latest_tag() {
python3 -c "
import sys, json
data = json.load(sys.stdin)
for r in data:
if not r.get('prerelease'):
print(r['tag_name'])
tag = r.get('tag_name', '')
if not r.get('prerelease') and tag.startswith('v') and r.get('assets'):
print(tag)
break
"
}
else
_json_latest_tag() {
grep -oE '"tag_name":[[:space:]]*"v[0-9]+\.[0-9]+\.[0-9]+"' | head -1 | sed -E 's/.*"v/v/; s/"$//'
}
fi
# ────────────────────────────────────────────
@@ -95,14 +102,79 @@ fi
get_latest_tag() {
info "Fetching latest release from GitHub API..."
local releases
releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API."
local releases=""
releases="$(curl -fsSL -H "User-Agent: install-dua-sh" "${API_URL}" 2>/dev/null)" || true
local tag=""
if [[ -n "$releases" ]]; then
tag="$(echo "$releases" | _json_latest_tag 2>/dev/null || true)"
fi
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"
}
# ────────────────────────────────────────────
# Install binary from GitHub releases
# ────────────────────────────────────────────
install_from_github() {
local target=""
case "$OS" in
Darwin)
case "$ARCH" in
x86_64 | amd64) target="x86_64-apple-darwin" ;;
aarch64 | arm64) target="aarch64-apple-darwin" ;;
*) error "Unsupported macOS architecture: $ARCH" ;;
esac
;;
Linux)
case "$ARCH" in
x86_64 | amd64) target="x86_64-unknown-linux-musl" ;;
aarch64 | arm64) target="aarch64-unknown-linux-musl" ;;
armv7l | armhf) target="arm-unknown-linux-gnueabihf" ;;
riscv64) target="riscv64gc-unknown-linux-gnu" ;;
*) error "Unsupported Linux architecture: $ARCH" ;;
esac
;;
esac
local tag
tag="$(echo "$releases" | _json_latest_tag)"
tag="$(get_latest_tag)"
local filename="dua-${tag}-${target}.tar.gz"
local download_url="https://github.com/${REPO}/releases/download/${tag}/${filename}"
[[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag."
echo "$tag"
info "Downloading: $download_url"
TMPDIR="$(mktemp -d)"
trap '[[ -d "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"' EXIT
curl -fsSL --progress-bar -o "${TMPDIR}/${filename}" "$download_url" ||
error "Download failed: $download_url"
info "Extracting..."
tar -xzf "${TMPDIR}/${filename}" -C "$TMPDIR"
# Find the dua binary
local dua_bin
dua_bin="$(find "$TMPDIR" -maxdepth 3 -type f -name "dua" | head -1)"
[[ -z "$dua_bin" ]] && error "Cannot find 'dua' binary in the archive."
mkdir -p "$INSTALL_DIR"
cp "$dua_bin" "${INSTALL_DIR}/dua"
chmod +x "${INSTALL_DIR}/dua"
export PATH="${INSTALL_DIR}:${PATH}"
success "dua ${tag} installed to ${INSTALL_DIR}/dua"
# PATH instructions
if ! grep -qs "${INSTALL_DIR}" "${HOME}/.bashrc" "${HOME}/.zshrc" 2>/dev/null; then
if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then
warn "'${INSTALL_DIR}' is not in your PATH."
fi
info "Ensure '${INSTALL_DIR}' is in your PATH (e.g. ~/.bashrc or ~/.zshrc):"
info " export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
}
# ────────────────────────────────────────────
@@ -112,95 +184,23 @@ info "Installing dua (Disk Usage Analyzer)..."
if command -v dua &>/dev/null; then
CURRENT_VER="$(dua --version 2>/dev/null || echo 'unknown')"
info "dua is already installed ($CURRENT_VER). Skipping installation."
else
info "dua is already installed ($CURRENT_VER). Updating/checking latest version..."
fi
case "$OS" in
Darwin)
ensure_brew
brew install dua-cli
if command -v brew &>/dev/null && brew install dua-cli 2>/dev/null; then
success "dua installed via Homebrew"
else
info "Installing dua from GitHub releases..."
install_from_github
fi
;;
Linux)
# Try package managers first
DUA_INSTALLED=false
if command -v apt-get &>/dev/null; then
info "Trying apt-get..."
sudo apt-get update -y 2>/dev/null || true
if sudo apt-get install -y dua-cli 2>/dev/null; then
DUA_INSTALLED=true
success "dua installed via apt-get"
fi
fi
if [ "$DUA_INSTALLED" = "false" ] && command -v dnf &>/dev/null; then
info "Trying dnf..."
if sudo dnf install -y dua-cli 2>/dev/null; then
DUA_INSTALLED=true
success "dua installed via dnf"
fi
fi
if [ "$DUA_INSTALLED" = "false" ] && command -v pacman &>/dev/null; then
info "Trying pacman..."
if sudo pacman -S --noconfirm dua-cli 2>/dev/null; then
DUA_INSTALLED=true
success "dua installed via pacman"
fi
fi
if [ "$DUA_INSTALLED" = "false" ] && command -v zypper &>/dev/null; then
info "Trying zypper..."
if sudo zypper -n install dua-cli 2>/dev/null; then
DUA_INSTALLED=true
success "dua installed via zypper"
fi
fi
# Fallback: download binary from GitHub releases
if [ "$DUA_INSTALLED" = "false" ]; then
info "No package manager had dua-cli. Downloading from GitHub releases..."
# Determine target
case "$ARCH" in
x86_64 | amd64) TARGET="x86_64-unknown-linux-musl" ;;
aarch64 | arm64) TARGET="aarch64-unknown-linux-musl" ;;
*) error "Unsupported Linux architecture: $ARCH" ;;
esac
TAG="$(get_latest_tag)"
FILENAME="dua-${TAG}-${TARGET}.tar.gz"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${FILENAME}"
info "Downloading: $DOWNLOAD_URL"
TMPDIR="$(mktemp -d)"
trap '[[ -d "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"' EXIT
curl -fsSL --progress-bar -o "${TMPDIR}/${FILENAME}" "$DOWNLOAD_URL" ||
error "Download failed: $DOWNLOAD_URL"
info "Extracting..."
tar -xzf "${TMPDIR}/${FILENAME}" -C "$TMPDIR"
# Find the dua binary
DUA_BIN="$(find "$TMPDIR" -maxdepth 3 -type f -name "dua" | head -1)"
[[ -z "$DUA_BIN" ]] && error "Cannot find 'dua' binary in the archive."
mkdir -p "$INSTALL_DIR"
cp "$DUA_BIN" "${INSTALL_DIR}/dua"
chmod +x "${INSTALL_DIR}/dua"
success "dua installed to ${INSTALL_DIR}/dua"
# PATH instructions
if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
warn "'${INSTALL_DIR}' is not in your PATH."
warn "Add this line to your ~/.bashrc or ~/.zshrc:"
warn " export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
fi
info "Installing dua from GitHub releases..."
install_from_github
;;
esac
fi
# Verify dua is available
if command -v dua &>/dev/null; then