Skip to main content

Agent Skills Framework Extension

Native Installer Patterns Skill

When to Use This Skill

Use this skill when implementing native installer patterns patterns in your codebase.

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Universal install scripts for macOS, Linux, and Windows with platform detection, PATH setup, checksum verification, and self-update capabilities.

Core Capabilities

  1. Universal Installer - Single script for all platforms
  2. Platform Detection - Automatic OS/arch identification
  3. PATH Management - Shell profile configuration
  4. Checksum Verification - SHA256 validation
  5. Self-Update - Built-in update mechanism

Universal Install Script (Bash)

#!/usr/bin/env bash
# install.sh - Universal installer for mytool
# Usage: curl -fsSL https://example.com/install.sh | bash
# Or: wget -qO- https://example.com/install.sh | bash

set -euo pipefail

# Configuration
REPO="org/mytool"
INSTALL_DIR="${MYTOOL_INSTALL_DIR:-$HOME/.mytool}"
BIN_DIR="$INSTALL_DIR/bin"
VERSION="${MYTOOL_VERSION:-latest}"
GITHUB_API="https://api.github.com"
GITHUB_DOWNLOAD="https://github.com/${REPO}/releases/download"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_step() { echo -e "${BLUE}==>${NC} $1"; }

# Detect platform
detect_platform() {
local os arch

case "$(uname -s)" in
Linux*)
os="linux"
;;
Darwin*)
os="darwin"
;;
MINGW*|MSYS*|CYGWIN*)
os="windows"
;;
*)
log_error "Unsupported OS: $(uname -s)"
exit 1
;;
esac

case "$(uname -m)" in
x86_64|amd64)
arch="x64"
;;
arm64|aarch64)
arch="arm64"
;;
i386|i686)
arch="x86"
;;
*)
log_error "Unsupported architecture: $(uname -m)"
exit 1
;;
esac

echo "${os}-${arch}"
}

# Get latest version from GitHub
get_latest_version() {
local url="${GITHUB_API}/repos/${REPO}/releases/latest"

if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/'
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$url" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/'
else
log_error "Neither curl nor wget found. Please install one of them."
exit 1
fi
}

# Download file with progress
download_file() {
local url="$1"
local output="$2"

log_step "Downloading: $(basename "$output")"

if command -v curl >/dev/null 2>&1; then
curl -fsSL --progress-bar -o "$output" "$url"
elif command -v wget >/dev/null 2>&1; then
wget -q --show-progress -O "$output" "$url"
else
log_error "Neither curl nor wget found"
exit 1
fi
}

# Verify checksum
verify_checksum() {
local file="$1"
local checksum_file="$2"

log_step "Verifying checksum..."

if command -v sha256sum >/dev/null 2>&1; then
(cd "$(dirname "$file")" && sha256sum -c "$(basename "$checksum_file")")
elif command -v shasum >/dev/null 2>&1; then
(cd "$(dirname "$file")" && shasum -a 256 -c "$(basename "$checksum_file")")
else
log_warn "No SHA256 tool found, skipping verification"
return 0
fi
}

# Extract archive
extract_archive() {
local archive="$1"
local dest="$2"

log_step "Extracting archive..."

case "$archive" in
*.tar.gz|*.tgz)
tar -xzf "$archive" -C "$dest"
;;
*.zip)
unzip -q "$archive" -d "$dest"
;;
*)
log_error "Unsupported archive format: $archive"
exit 1
;;
esac
}

# Update PATH in shell profile
update_path() {
local shell_profile=""

case "$SHELL" in
*/bash)
shell_profile="$HOME/.bashrc"
[[ -f "$HOME/.bash_profile" ]] && shell_profile="$HOME/.bash_profile"
;;
*/zsh)
shell_profile="$HOME/.zshrc"
;;
*/fish)
shell_profile="$HOME/.config/fish/config.fish"
;;
*)
log_warn "Unknown shell: $SHELL. Please manually add $BIN_DIR to your PATH"
return
;;
esac

# Check if already in PATH
if grep -q "$BIN_DIR" "$shell_profile" 2>/dev/null; then
log_info "PATH already configured in $shell_profile"
return
fi

log_step "Adding to PATH in $shell_profile"

# Create profile if doesn't exist
touch "$shell_profile"

# Add to PATH
if [[ "$SHELL" == */fish ]]; then
echo "" >> "$shell_profile"
echo "# mytool" >> "$shell_profile"
echo "set -gx PATH \$PATH $BIN_DIR" >> "$shell_profile"
else
echo "" >> "$shell_profile"
echo "# mytool" >> "$shell_profile"
echo "export PATH=\"\$PATH:$BIN_DIR\"" >> "$shell_profile"
fi

log_info "Added $BIN_DIR to PATH"
log_warn "Restart your shell or run: source $shell_profile"
}

# Main installation
install_mytool() {
log_info "Installing mytool..."

# Detect platform
local platform
platform=$(detect_platform)
log_info "Detected platform: $platform"

# Get version
if [[ "$VERSION" == "latest" ]]; then
VERSION=$(get_latest_version)
fi
log_info "Installing version: $VERSION"

# Determine archive format
local ext="tar.gz"
[[ "$platform" == windows-* ]] && ext="zip"

# Download URLs
local filename="mytool-${platform}.${ext}"
local download_url="${GITHUB_DOWNLOAD}/v${VERSION}/${filename}"
local checksum_url="${download_url}.sha256"

# Create temp directory
local tmpdir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

# Download binary and checksum
download_file "$download_url" "${tmpdir}/${filename}"
download_file "$checksum_url" "${tmpdir}/${filename}.sha256"

# Verify checksum
verify_checksum "${tmpdir}/${filename}" "${tmpdir}/${filename}.sha256"

# Create install directory
mkdir -p "$BIN_DIR"

# Extract archive
extract_archive "${tmpdir}/${filename}" "$tmpdir"

# Find binary in extracted files
local binary_name="mytool"
[[ "$platform" == windows-* ]] && binary_name="mytool.exe"

local binary_path
binary_path=$(find "$tmpdir" -name "$binary_name" -type f | head -1)

if [[ -z "$binary_path" ]]; then
log_error "Binary not found in archive"
exit 1
fi

# Install binary
cp "$binary_path" "${BIN_DIR}/${binary_name}"
chmod +x "${BIN_DIR}/${binary_name}"

log_info "Installed to: ${BIN_DIR}/${binary_name}"

# Update PATH
update_path

# Verify installation
if command -v mytool >/dev/null 2>&1 || [[ -x "${BIN_DIR}/${binary_name}" ]]; then
log_info "Installation successful!"
log_info "Run 'mytool --version' to verify"
else
log_warn "Installation complete but mytool not in PATH yet"
log_warn "Restart your shell or add manually: export PATH=\"\$PATH:$BIN_DIR\""
fi
}

# Uninstall
uninstall_mytool() {
log_info "Uninstalling mytool..."

if [[ -d "$INSTALL_DIR" ]]; then
rm -rf "$INSTALL_DIR"
log_info "Removed: $INSTALL_DIR"
fi

log_warn "Please manually remove PATH entries from your shell profile"
log_info "Uninstall complete"
}

# Update
update_mytool() {
log_info "Updating mytool..."
VERSION="latest"
install_mytool
}

# CLI
main() {
case "${1:-install}" in
install)
install_mytool
;;
uninstall)
uninstall_mytool
;;
update)
update_mytool
;;
--version)
echo "mytool installer v1.0.0"
;;
--help)
cat <<EOF
mytool Installer

Usage:
curl -fsSL https://example.com/install.sh | bash
curl -fsSL https://example.com/install.sh | bash -s -- [command]

Commands:
install Install mytool (default)
uninstall Uninstall mytool
update Update to latest version
--version Show installer version
--help Show this help

Environment variables:
MYTOOL_VERSION Version to install (default: latest)
MYTOOL_INSTALL_DIR Installation directory (default: ~/.mytool)

Examples:
# Install latest version
curl -fsSL https://example.com/install.sh | bash

# Install specific version
curl -fsSL https://example.com/install.sh | MYTOOL_VERSION=1.2.3 bash

# Uninstall
curl -fsSL https://example.com/install.sh | bash -s -- uninstall
EOF
;;
*)
log_error "Unknown command: $1"
log_error "Run with --help for usage"
exit 1
;;
esac
}

main "$@"

Windows PowerShell Installer

# install.ps1 - Windows installer for mytool
# Usage: iwr -useb https://example.com/install.ps1 | iex

param(
[string]$Version = "latest",
[string]$InstallDir = "$env:USERPROFILE\.mytool"
)

$ErrorActionPreference = "Stop"

# Configuration
$Repo = "org/mytool"
$GithubAPI = "https://api.github.com"
$GithubDownload = "https://github.com/$Repo/releases/download"
$BinDir = Join-Path $InstallDir "bin"

# Logging functions
function Write-ColorOutput {
param([string]$Message, [string]$Color = "White")
Write-Host $Message -ForegroundColor $Color
}

function Log-Info { Write-ColorOutput "[INFO] $args" -Color Green }
function Log-Warn { Write-ColorOutput "[WARN] $args" -Color Yellow }
function Log-Error { Write-ColorOutput "[ERROR] $args" -Color Red }
function Log-Step { Write-ColorOutput "==> $args" -Color Blue }

# Get latest version
function Get-LatestVersion {
$url = "$GithubAPI/repos/$Repo/releases/latest"
try {
$response = Invoke-RestMethod -Uri $url
return $response.tag_name.TrimStart('v')
} catch {
Log-Error "Failed to fetch latest version: $_"
exit 1
}
}

# Download file
function Download-File {
param([string]$Url, [string]$Output)

Log-Step "Downloading: $(Split-Path $Output -Leaf)"

try {
# Use BITS transfer for progress indication
Start-BitsTransfer -Source $Url -Destination $Output
} catch {
# Fallback to Invoke-WebRequest
Invoke-WebRequest -Uri $Url -OutFile $Output
}
}

# Verify checksum
function Verify-Checksum {
param([string]$File, [string]$ChecksumFile)

Log-Step "Verifying checksum..."

$expectedHash = (Get-Content $ChecksumFile).Split()[0]
$actualHash = (Get-FileHash $File -Algorithm SHA256).Hash.ToLower()

if ($expectedHash -ne $actualHash) {
Log-Error "Checksum mismatch!"
Log-Error "Expected: $expectedHash"
Log-Error "Actual: $actualHash"
exit 1
}

Log-Info "Checksum verified"
}

# Update PATH
function Update-Path {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")

if ($currentPath -like "*$BinDir*") {
Log-Info "PATH already configured"
return
}

Log-Step "Adding to PATH..."

$newPath = $currentPath + ";$BinDir"
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")

# Update current session PATH
$env:Path += ";$BinDir"

Log-Info "Added $BinDir to PATH"
Log-Warn "Restart your terminal for PATH changes to take effect"
}

# Main installation
function Install-MyTool {
Log-Info "Installing mytool for Windows..."

# Get version
if ($Version -eq "latest") {
$Version = Get-LatestVersion
}
Log-Info "Installing version: $Version"

# Download URLs
$filename = "mytool-windows-x64.zip"
$downloadUrl = "$GithubDownload/v$Version/$filename"
$checksumUrl = "$downloadUrl.sha256"

# Create temp directory
$tempDir = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ([System.Guid]::NewGuid()))

try {
# Download binary and checksum
$archivePath = Join-Path $tempDir $filename
$checksumPath = Join-Path $tempDir "$filename.sha256"

Download-File -Url $downloadUrl -Output $archivePath
Download-File -Url $checksumUrl -Output $checksumPath

# Verify checksum
Verify-Checksum -File $archivePath -ChecksumFile $checksumPath

# Extract archive
Log-Step "Extracting archive..."
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force

# Create install directory
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null

# Find and copy binary
$binaryPath = Get-ChildItem -Path $tempDir -Filter "mytool.exe" -Recurse | Select-Object -First 1

if (-not $binaryPath) {
Log-Error "Binary not found in archive"
exit 1
}

Copy-Item -Path $binaryPath.FullName -Destination (Join-Path $BinDir "mytool.exe") -Force
Log-Info "Installed to: $(Join-Path $BinDir 'mytool.exe')"

# Update PATH
Update-Path

# Verify installation
try {
& (Join-Path $BinDir "mytool.exe") --version | Out-Null
Log-Info "Installation successful!"
Log-Info "Run 'mytool --version' to verify"
} catch {
Log-Warn "Installation complete but mytool not in PATH yet"
Log-Warn "Restart your terminal or add manually to PATH: $BinDir"
}

} finally {
# Cleanup
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}

# Uninstall
function Uninstall-MyTool {
Log-Info "Uninstalling mytool..."

if (Test-Path $InstallDir) {
Remove-Item -Path $InstallDir -Recurse -Force
Log-Info "Removed: $InstallDir"
}

# Remove from PATH
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
$newPath = ($currentPath.Split(';') | Where-Object { $_ -ne $BinDir }) -join ';'
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")

Log-Info "Uninstall complete"
}

# Run installation
Install-MyTool

Self-Update Mechanism

#!/usr/bin/env bash
# Built into mytool binary - self-update command

mytool_self_update() {
local current_version
current_version=$(mytool --version | awk '{print $NF}')

local latest_version
latest_version=$(curl -fsSL "https://api.github.com/repos/org/mytool/releases/latest" | \
grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/')

if [[ "$current_version" == "$latest_version" ]]; then
echo "✅ mytool is already up to date ($current_version)"
return 0
fi

echo "📦 Updating mytool: $current_version$latest_version"

# Re-run installer with latest version
export MYTOOL_VERSION="$latest_version"
curl -fsSL https://example.com/install.sh | bash

echo "✅ Updated to version $latest_version"
}

Usage Examples

Create Universal Installer

Apply native-installer-patterns skill to create bash installer with platform detection and PATH configuration

Windows PowerShell Installer

Apply native-installer-patterns skill to generate PowerShell installer for Windows with PATH updates

Self-Update Command

Apply native-installer-patterns skill to implement self-update mechanism with version checking

Integration Points

  • binary-distribution-patterns - Download and extract binaries
  • npm-packaging-patterns - npm postinstall integration
  • security-audit-patterns - Checksum verification and validation

Success Output

When successful, this skill MUST output:

✅ SKILL COMPLETE: native-installer-patterns

Completed:
- [x] Universal installer script created (Bash for macOS/Linux)
- [x] Windows PowerShell installer implemented
- [x] Platform detection and PATH management configured
- [x] Checksum verification integrated (SHA256)
- [x] Self-update mechanism implemented

Outputs:
- install.sh (universal Bash installer)
- install.ps1 (Windows PowerShell installer)
- Platform detection functions tested on macOS/Linux/Windows
- PATH update verified in shell profiles (bash/zsh/fish)
- Installation tested with version verification

Completion Checklist

Before marking this skill as complete, verify:

  • Universal installer script (install.sh) created and executable
  • Windows installer script (install.ps1) created
  • Platform detection working for macOS/Linux/Windows
  • Architecture detection working (x64/arm64/x86)
  • Download mechanisms tested (curl and wget fallback)
  • SHA256 checksum verification implemented
  • Archive extraction working (tar.gz, zip)
  • PATH update working for bash/zsh/fish shells
  • Self-update command functional
  • Installation verified with --version command
  • Uninstall mechanism tested

Failure Indicators

This skill has FAILED if:

  • ❌ Installer script fails on target platform (macOS, Linux, or Windows)
  • ❌ Platform/architecture detection returns "unsupported" for common platforms
  • ❌ Download fails without trying fallback method (curl → wget)
  • ❌ Checksum verification skipped or always passes
  • ❌ Binary not found in archive after extraction
  • ❌ PATH not updated in shell profile
  • ❌ Installation succeeds but binary not executable
  • ❌ Self-update mechanism fails to detect new versions

When NOT to Use

Do NOT use this skill when:

  • Application is distributed via package managers only (apt, brew, npm, pip)
  • Binary releases are not published (GitHub releases, CDN)
  • Application requires complex installation (database setup, config generation)
  • Installation is containerized only (Docker, Kubernetes)
  • Cross-platform support not needed (single platform binary)
  • User base is technical and prefers manual installation
  • Platform-specific installers already exist (.deb, .rpm, .pkg, .msi)
  • Application is web-based only (no local installation)

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
Skipping checksum verificationSecurity vulnerabilityAlways verify SHA256 before execution
Platform-specific scripts onlyUsers on other platforms can't installCreate universal installer with detection
Hardcoded versionCan't update easilyFetch latest version from API
No PATH updateUsers must configure manuallyAuto-update shell profiles
Missing error messagesSilent failures confuse usersLog clear errors with troubleshooting steps
No cleanup on failureLeaves temp filesUse trap for cleanup in Bash
Overwriting without backupUsers lose custom configsBackup existing installations
No uninstall mechanismUsers stuck with toolProvide uninstall command

Principles

This skill embodies:

  • #1 First Principles - Understand platform differences before creating installer
  • #2 Self-Provisioning - Installer handles all dependencies and configuration
  • #5 Eliminate Ambiguity - Clear error messages and installation status
  • #6 Clear, Understandable, Explainable - Installation steps logged to user
  • #8 No Assumptions - Detect platform/shell, don't assume environment
  • #9 Keep It Simple - Single script handles all platforms when possible

Full Standard: CODITECT-STANDARD-AUTOMATION.md