commit a07e58ce04e5189ebaeca2bc08d62b35d04c5f0f Author: Jerry Chae Date: Tue May 12 15:12:52 2026 +0900 Initial commit; from install-bottom diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c14430 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# macOS +.DS_Store +._* + +# Windows +Thumbs.db +desktop.ini + +# IDE/Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Logs +*.log + +# Non-shell script files (not .sh, .bat, .ps1) +*.exe +*.dll +*.zip +*.tar.gz +*.7z +*.pdf +*.doc +*.docx +*.xls +*.xlsx +*.txt +!README.txt + +# Build artifacts +build/ +dist/ +*.o +*.a +*.so + +# Dependencies +node_modules/ +vendor/ diff --git a/WORKLOG.md b/WORKLOG.md new file mode 100644 index 0000000..5255513 --- /dev/null +++ b/WORKLOG.md @@ -0,0 +1,10 @@ +## 작업 내용 +- 현재 작업: bottom 설치 스크립트 분석 +- 디렉토리: /Users/mcchae/work/util +- 완료: 디렉토리 구조 분석 +- 다음 작업: ___ (여기에 적어주세요) + +## 중요 파일 +- install-bottom.sh: 메인 설치 스크립트 (Linux/macOS) +- install-bottom.ps1: Windows PowerShell용 +- install-bottom.bat: Windows CMD용 diff --git a/install-bottom.bat b/install-bottom.bat new file mode 100644 index 0000000..99523d7 --- /dev/null +++ b/install-bottom.bat @@ -0,0 +1,69 @@ +@echo off +:: ============================================================ +:: install-bottom.bat +:: bottom(btm) 프로세스 모니터 설치 스크립트 (Windows 배치파일) +:: +:: 이 파일은 PowerShell 스크립트(install-bottom.ps1)를 호출합니다. +:: PowerShell 5.1 이상이 필요합니다 (Windows 10/11 기본 탑재). +:: +:: 사용법: +:: install-bottom.bat +:: install-bottom.bat "C:\tools\bin" <- 설치 경로 지정 +:: ============================================================ + +:: ── UTF-8 코드페이지로 전환(한글 출력 깨짐 방지) ── +chcp 65001 >nul + +setlocal EnableDelayedExpansion + +echo [INFO] install-bottom.bat 시작 + +:: ── PowerShell 존재 여부 확인 ──────────────────────────────── +where powershell >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [ERROR] PowerShell을 찾을 수 없습니다. + echo Windows 7 이상에서는 PowerShell이 기본 탑재됩니다. + pause + exit /b 1 +) + +:: ── 이 배치파일과 같은 폴더의 install-bottom.ps1 위치 확인 ── +set "PS1_PATH=%~dp0install-bottom.ps1" +if not exist "%PS1_PATH%" ( + echo [ERROR] install-bottom.ps1 파일을 찾을 수 없습니다. + echo 이 배치파일과 같은 폴더에 install-bottom.ps1 이 있어야 합니다. + echo 위치: %PS1_PATH% + pause + exit /b 1 +) + +:: ── 설치 경로 파라미터 처리 ──────────────────────────────── +set "EXTRA_ARGS=" +if not "%~1"=="" ( + set "EXTRA_ARGS=-InstallDir ""%~1""" + echo [INFO] 설치 경로 지정: %~1 +) + +echo [INFO] PowerShell 스크립트를 실행합니다... +echo. + +:: ── PowerShell 실행 ───────────────────────────────────────── +:: -ExecutionPolicy Bypass : 실행 정책 우회 (현재 사용자 설정 변경 없음) +:: -NoProfile : 프로필 로드 생략 (빠른 시작) +:: -NonInteractive : 대화형 프롬프트 억제 +powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive ^ + -File "%PS1_PATH%" %EXTRA_ARGS% + +set "PS_EXIT=%ERRORLEVEL%" + +echo. +if %PS_EXIT% equ 0 ( + echo [INFO] 설치가 완료되었습니다. + echo 새 터미널(cmd/PowerShell)을 열면 'btm' 명령어를 사용할 수 있습니다. +) else ( + echo [ERROR] 설치 중 오류가 발생했습니다. (종료 코드: %PS_EXIT%) +) + +echo. +pause +exit /b %PS_EXIT% diff --git a/install-bottom.ps1 b/install-bottom.ps1 new file mode 100644 index 0000000..349f8b2 --- /dev/null +++ b/install-bottom.ps1 @@ -0,0 +1,232 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + bottom(btm) 프로세스 모니터를 GitHub Nightly 최신 버전으로 설치합니다. + +.DESCRIPTION + https://github.com/ClementTsang/bottom/releases 에서 최신 Nightly 릴리스를 + 자동으로 탐지하고, 현재 Windows 아키텍처에 맞는 zip 파일을 다운로드하여 + 설치합니다. + + 지원 아키텍처: + - x64 (AMD64 / Intel 64bit) + - ARM64 (Snapdragon X Elite, Qualcomm ARM 등) + +.PARAMETER InstallDir + btm.exe 를 설치할 디렉토리. 기본값: $env:USERPROFILE\.local\bin + +.PARAMETER AddToPath + InstallDir 을 영구적으로 사용자 PATH에 추가할지 여부. 기본값: $true + +.EXAMPLE + .\install-bottom.ps1 + .\install-bottom.ps1 -InstallDir "C:\tools\bin" + .\install-bottom.ps1 -AddToPath $false + +.NOTES + 실행 정책 오류 발생 시: + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +#> + +[CmdletBinding()] +param( + [string] $InstallDir = "$env:USERPROFILE\.local\bin", + [bool] $AddToPath = $true +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ──────────────────────────────────────────── +# 상수 +# ──────────────────────────────────────────── +$REPO = "ClementTsang/bottom" +$API_URL = "https://api.github.com/repos/$REPO/releases" + +# ──────────────────────────────────────────── +# 유틸리티 함수 +# ──────────────────────────────────────────── +function Write-Info { param([string]$msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan } +function Write-Warn { param([string]$msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } +function Write-Success { param([string]$msg) Write-Host "[OK] $msg" -ForegroundColor Green } +function Write-Err { param([string]$msg) Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 } + +# ──────────────────────────────────────────── +# TLS 1.2 강제 (Windows 7/8.1 대비) +# ──────────────────────────────────────────── +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# ──────────────────────────────────────────── +# 아키텍처 감지 +# ──────────────────────────────────────────── +function Get-Platform { + # 1) 환경변수를 먼저 확인 (Windows on ARM의 32-bit PowerShell에서도 OS 아키를 정확히 판별) + # - 네이티브 프로세스 : PROCESSOR_ARCHITECTURE = AMD64 / ARM64 / x86 + # - WOW64(32bit on 64bit): PROCESSOR_ARCHITECTURE = x86, + # PROCESSOR_ARCHITEW6432 = AMD64 또는 ARM64 (실제 OS 아키) + $envArch = $env:PROCESSOR_ARCHITEW6432 + if (-not $envArch) { $envArch = $env:PROCESSOR_ARCHITECTURE } + + switch -Regex ($envArch) { + '^AMD64$' { return "x86_64-pc-windows-msvc" } + '^ARM64$' { return "aarch64-pc-windows-msvc" } + '^x86$' { return "i686-pc-windows-msvc" } + } + + # 2) Fallback: .NET RuntimeInformation (PS 5.1 + .NET Framework 4.7.1+ 필요) + 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 { Write-Err "지원하지 않는 아키텍처: $arch" } + } + } catch { + Write-Err "아키텍처 감지에 실패했습니다: $_" + } +} + +# ──────────────────────────────────────────── +# 최신 Nightly 릴리스 태그 가져오기 +# ──────────────────────────────────────────── +function Get-LatestNightlyTag { + Write-Info "GitHub API로 최신 Nightly 릴리스 정보를 조회합니다..." + + try { + $releases = Invoke-RestMethod -Uri $API_URL -UseBasicParsing ` + -Headers @{ 'User-Agent' = 'install-bottom-ps1' } + } catch { + Write-Err "GitHub API 호출에 실패했습니다: $_" + } + + # prerelease=true 이고 tag_name 이 'nightly-' 로 시작하는 첫 번째 항목 + $nightly = $releases | Where-Object { + $_.prerelease -eq $true -and $_.tag_name -like 'nightly-*' + } | Select-Object -First 1 + + if (-not $nightly) { + Write-Err "Nightly 릴리스를 찾을 수 없습니다." + } + + return $nightly.tag_name +} + +# ──────────────────────────────────────────── +# 다운로드 URL 조합 +# ──────────────────────────────────────────── +function Get-DownloadUrl { + param([string]$Tag, [string]$Triple) + $filename = "bottom_${Triple}.zip" + return "https://github.com/$REPO/releases/download/$Tag/$filename" +} + +# ──────────────────────────────────────────── +# 다운로드 & 압축 해제 & 설치 +# ──────────────────────────────────────────── +function Install-Bottom { + param([string]$Url) + + # 임시 디렉토리 생성 + $tmpDir = Join-Path $env:TEMP "bottom_install_$(Get-Random)" + New-Item -ItemType Directory -Path $tmpDir | Out-Null + + try { + $zipPath = Join-Path $tmpDir "bottom.zip" + + Write-Info "다운로드 중: $Url" + try { + # PowerShell 5+ 에서 진행률 표시 + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing + $ProgressPreference = 'Continue' + } catch { + Write-Err "다운로드 실패: $_" + } + + Write-Info "압축 해제 중..." + Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force + + # btm.exe 찾기 + $btmExe = Get-ChildItem -Path $tmpDir -Recurse -Filter "btm.exe" | + Select-Object -First 1 + if (-not $btmExe) { + Write-Err "압축 파일에서 'btm.exe'를 찾을 수 없습니다." + } + + # 설치 디렉토리 생성 + if (-not (Test-Path $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + } + + $destPath = Join-Path $InstallDir "btm.exe" + Write-Info "설치 중: $destPath" + Copy-Item -Path $btmExe.FullName -Destination $destPath -Force + + Write-Success "설치 완료!" + + # 버전 확인 + Write-Info "버전 확인:" + & $destPath --version + + } finally { + # 임시 파일 정리 + if (Test-Path $tmpDir) { + Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +# ──────────────────────────────────────────── +# PATH 등록 (사용자 환경변수) +# ──────────────────────────────────────────── +function Add-ToUserPath { + param([string]$Dir) + + $currentPath = [Environment]::GetEnvironmentVariable('PATH', 'User') + if (-not $currentPath) { $currentPath = '' } + # 결과가 단일 항목일 때도 배열로 강제 (-contains 안전성 확보) + $dirs = @($currentPath -split ';' | Where-Object { $_ -ne '' }) + + if ($dirs -contains $Dir) { + Write-Info "'$Dir' 은 이미 PATH에 등록되어 있습니다." + return + } + + $newPath = ($dirs + $Dir) -join ';' + [Environment]::SetEnvironmentVariable('PATH', $newPath, 'User') + + # 현재 세션에도 반영 + $env:PATH = "$env:PATH;$Dir" + + Write-Success "'$Dir' 을 사용자 PATH에 영구 등록했습니다." + Write-Warn "새 터미널을 열어야 PATH 변경이 적용됩니다." +} + +# ──────────────────────────────────────────── +# 메인 +# ──────────────────────────────────────────── +function Main { + Write-Info "===== bottom(btm) 설치 스크립트 (Windows PowerShell) =====" + + $triple = Get-Platform + Write-Info "감지된 플랫폼: $triple" + + $tag = Get-LatestNightlyTag + Write-Info "최신 Nightly 태그: $tag" + + $url = Get-DownloadUrl -Tag $tag -Triple $triple + Install-Bottom -Url $url + + if ($AddToPath) { + Add-ToUserPath -Dir $InstallDir + } else { + Write-Warn "PATH 자동 등록을 건너뜁니다." + Write-Warn "수동으로 PATH에 추가하세요: $InstallDir" + } + + Write-Success "모든 작업이 완료되었습니다." + Write-Info "사용법: btm" +} + +Main diff --git a/install-bottom.sh b/install-bottom.sh new file mode 100755 index 0000000..5d1a028 --- /dev/null +++ b/install-bottom.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2086 + +# https://github.com/ClementTsang/bottom/releases 에서 Nightly 마지막 버전 목록을 확인하고 +# 현재 OS와 CPU 등을 파악한 후 다운로드 받아서 설치 +# +# 지원 플랫폼: +# macOS : Apple Silicon (arm64), Intel (x86_64) +# Linux : x86_64, aarch64(RPi4/5), armv7(RPi2/3 32bit), armv6(RPi Zero/1), i686 +# FreeBSD : x86_64 +# Windows : Git Bash / MSYS2 / Cygwin 환경에서 .zip 설치 +# (네이티브 PowerShell/CMD는 지원 안 함 → WSL 권장) + +set -euo pipefail + +# ──────────────────────────────────────────── +# 상수 정의 +# ──────────────────────────────────────────── +REPO="ClementTsang/bottom" +API_URL="https://api.github.com/repos/${REPO}/releases" +INSTALL_DIR="${HOME}/.local/bin" + +# ──────────────────────────────────────────── +# 유틸리티 함수 (모두 stderr 출력) +# ──────────────────────────────────────────── +info() { echo "[INFO] $*" >&2; } +warn() { echo "[WARN] $*" >&2; } +error() { echo "[ERROR] $*" >&2; exit 1; } +success() { echo "[OK] $*" >&2; } + +# ──────────────────────────────────────────── +# 필수 명령어 확인 +# ──────────────────────────────────────────── +for cmd in curl uname; do + command -v "$cmd" &>/dev/null || error "'$cmd' 명령어가 필요합니다." +done + +# jq가 없으면 python3으로 JSON 파싱 (fallback) +if command -v jq &>/dev/null; then + _json_nightly_tag() { + jq -r '[.[] | select(.prerelease==true and (.tag_name | startswith("nightly-")))] | .[0].tag_name' + } +else + command -v python3 &>/dev/null || error "'jq' 또는 'python3' 중 하나가 필요합니다." + _json_nightly_tag() { + python3 -c " +import sys, json +data = json.load(sys.stdin) +for r in data: + if r.get('prerelease') and r.get('tag_name','').startswith('nightly-'): + print(r['tag_name']) + break +" + } +fi + +# ──────────────────────────────────────────── +# OS / 아키텍처 감지 +# ──────────────────────────────────────────── +# 반환값 형식: "<플랫폼 트리플>:<압축포맷>" +# 압축포맷 = tar.gz | zip +detect_platform() { + local os arch + + os="$(uname -s)" + arch="$(uname -m)" + + case "$os" in + # ── macOS ────────────────────────────────────────────── + Darwin) + case "$arch" in + arm64) echo "aarch64-apple-darwin:tar.gz" ;; + x86_64) echo "x86_64-apple-darwin:tar.gz" ;; + *) error "지원하지 않는 macOS 아키텍처: $arch" ;; + esac + ;; + + # ── Linux (라즈베리파이 전 모델 포함) ────────────────── + Linux) + case "$arch" in + x86_64|amd64) + echo "x86_64-unknown-linux-musl:tar.gz" ;; + aarch64|arm64) + # RPi 4/5, 64bit OS + echo "aarch64-unknown-linux-musl:tar.gz" ;; + armv7l) + # RPi 2/3, 32bit OS (ARMv7) + echo "armv7-unknown-linux-musleabihf:tar.gz" ;; + armv6l) + # RPi Zero / 1 (ARMv6) + # bottom은 ARMv6용 사전 빌드 바이너리를 제공하지 않습니다. + # (armv7 musl/gnu 바이너리는 ARMv6에서 동작하지 않음) + error "ARMv6(RPi Zero/1)용 사전 빌드 바이너리가 제공되지 않습니다. 'cargo install bottom' 으로 소스 빌드를 권장합니다." ;; + i686|i386) + echo "i686-unknown-linux-musl:tar.gz" ;; + *) + error "지원하지 않는 Linux 아키텍처: $arch" ;; + esac + ;; + + # ── FreeBSD ──────────────────────────────────────────── + FreeBSD) + case "$arch" in + amd64|x86_64) echo "x86_64-unknown-freebsd:tar.gz" ;; + *) error "FreeBSD는 x86_64만 지원합니다 (현재: $arch)" ;; + esac + ;; + + # ── Windows (Git Bash / MSYS2 / Cygwin) ─────────────── + # 네이티브 PowerShell/CMD는 bash가 없으므로 이 스크립트 실행 불가 + # WSL은 Linux로 감지되므로 여기에 해당 안 됨 + MINGW*|MSYS*|CYGWIN*) + warn "Windows 환경(Git Bash/MSYS2/Cygwin)이 감지되었습니다." + warn "WSL(Windows Subsystem for Linux) 사용을 권장합니다." + # Windows에는 tar.exe(Windows 10 1803+)가 있어 .zip 처리 가능 + case "$arch" in + x86_64) echo "x86_64-pc-windows-msvc:zip" ;; + aarch64) echo "aarch64-pc-windows-msvc:zip" ;; + i686) echo "i686-pc-windows-msvc:zip" ;; + *) error "지원하지 않는 Windows 아키텍처: $arch" ;; + esac + ;; + + *) + error "지원하지 않는 OS: $os (uname -a: $(uname -a))" + ;; + esac +} + +# ──────────────────────────────────────────── +# 최신 Nightly 릴리스 태그 가져오기 +# ──────────────────────────────────────────── +get_latest_nightly_tag() { + info "GitHub API로 최신 Nightly 릴리스 정보를 조회합니다..." + + local releases + releases="$(curl -fsSL "${API_URL}")" || error "GitHub API 호출에 실패했습니다." + + local tag + tag="$(echo "$releases" | _json_nightly_tag)" + + [[ -z "$tag" || "$tag" == "null" ]] && error "Nightly 릴리스 태그를 찾을 수 없습니다." + echo "$tag" +} + +# ──────────────────────────────────────────── +# 다운로드 URL 조합 +# ──────────────────────────────────────────── +build_download_url() { + local tag="$1" + local triple="$2" # 플랫폼 트리플 (e.g. x86_64-apple-darwin) + local ext="$3" # tar.gz | zip + local filename="bottom_${triple}.${ext}" + echo "https://github.com/${REPO}/releases/download/${tag}/${filename}" +} + +# ──────────────────────────────────────────── +# 다운로드 & 압축 해제 & 설치 +# ──────────────────────────────────────────── +download_and_install() { + local url="$1" + local ext="$2" # tar.gz | zip + local is_windows="${3:-false}" + + local tmpdir + tmpdir="$(mktemp -d)" + trap '[[ -d "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT + + local filename + filename="${url##*/}" + + info "다운로드 중: $url" + curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" \ + || error "다운로드 실패: $url" + + info "압축 해제 중..." + case "$ext" in + tar.gz) + command -v tar &>/dev/null || error "'tar' 명령어가 필요합니다." + tar -xzf "${tmpdir}/${filename}" -C "$tmpdir" + ;; + zip) + if command -v unzip &>/dev/null; then + unzip -q "${tmpdir}/${filename}" -d "$tmpdir" + elif command -v tar &>/dev/null; then + # Windows 10+ tar.exe 는 zip 지원 + tar -xf "${tmpdir}/${filename}" -C "$tmpdir" + else + error "'unzip' 또는 'tar'(Windows 10+) 명령어가 필요합니다." + fi + ;; + *) + error "알 수 없는 압축 포맷: $ext" + ;; + esac + + # Windows는 btm.exe, 나머지는 btm + local bin_name="btm" + [[ "$is_windows" == "true" ]] && bin_name="btm.exe" + + local bin_src + bin_src="$(find "$tmpdir" -maxdepth 3 -type f -name "$bin_name" | head -1)" + [[ -z "$bin_src" ]] && error "압축 파일에서 '$bin_name' 실행파일을 찾을 수 없습니다." + + # 설치 디렉토리 생성 + mkdir -p "$INSTALL_DIR" + + local install_path="${INSTALL_DIR}/${bin_name}" + info "설치 중: ${install_path}" + cp "$bin_src" "$install_path" + chmod +x "$install_path" + + success "설치 완료!" + info "버전 확인:" + "$install_path" --version || true + + # PATH 안내 + if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then + warn "'${INSTALL_DIR}' 가 PATH에 없습니다." + warn "다음 줄을 ~/.bashrc 또는 ~/.zshrc 에 추가하세요:" + warn " export PATH=\"\$HOME/.local/bin:\$PATH\"" + fi +} + +# ──────────────────────────────────────────── +# 메인 +# ──────────────────────────────────────────── +main() { + local platform_info triple ext is_windows tag url + + platform_info="$(detect_platform)" + triple="${platform_info%%:*}" # ':' 앞부분 + ext="${platform_info##*:}" # ':' 뒷부분 + + info "감지된 플랫폼: $triple (압축 포맷: $ext)" + + # Windows 여부 판단 + is_windows="false" + [[ "$triple" == *"windows"* ]] && is_windows="true" + + tag="$(get_latest_nightly_tag)" + info "최신 Nightly 태그: $tag" + + url="$(build_download_url "$tag" "$triple" "$ext")" + download_and_install "$url" "$ext" "$is_windows" +} + +main "$@"