Skip to content

[core]: alpine‐tools.func

CanbiZ edited this page Dec 1, 2025 · 1 revision

Alpine-Tools.func Wiki

Alpine Linux-specific tool setup and package management module providing helper functions optimized for Alpine's apk package manager and minimal container environment.


📋 Table of Contents


Overview

Alpine-tools.func provides Alpine Linux-specific utilities:

  • ✅ Alpine apk package manager wrapper
  • ✅ GitHub release version checking and installation
  • ✅ Tool caching and version tracking
  • ✅ Progress reporting with pv (pipe viewer)
  • ✅ Network resolution helpers for Alpine
  • ✅ PATH persistence across sessions
  • ✅ Retry logic for failed downloads
  • ✅ Minimal dependencies philosophy (Alpine ~5MB containers)

Key Differences from Debian/Ubuntu

Feature Alpine Debian/Ubuntu
Package Manager apk apt-get, dpkg
Shell ash (dash variant) bash
Init System OpenRC systemd
Size ~5MB base ~100MB+ base
Libc musl glibc
Find getent Not installed Installed

Integration Pattern

#!/bin/sh  # Alpine uses ash, not bash
source <(curl -fsSL .../core.func)
source <(curl -fsSL .../alpine-tools.func)
load_functions

# Now Alpine-specific tool functions available
need_tool curl jq    # Install if missing
check_for_gh_release "myapp" "owner/repo"

Helper Functions

lower()

Purpose: Converts string to lowercase (portable ash function).

Signature:

lower()

Parameters:

  • $1 - String to convert

Returns: Lowercase string on stdout

Behavior:

# Alpine's tr works with character classes
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'

Usage Examples:

# Example 1: App name normalization
result=$(lower "MyApp")
echo "$result"  # Output: myapp

# Example 2: In variable assignment
app_dir=$(lower "$APPLICATION")
mkdir -p /opt/$app_dir

has()

Purpose: Checks if command is available in PATH.

Signature:

has()

Parameters:

  • $1 - Command name

Returns: 0 if available, 1 if not

Implementation:

has() {
  command -v "$1" >/dev/null 2>&1
}

Usage Examples:

# Example 1: Check availability
if has jq; then
  echo "jq is installed"
else
  echo "jq is not installed"
fi

# Example 2: In conditionals
has docker && docker ps || echo "Docker not installed"

need_tool()

Purpose: Ensures specified tools are installed, installs missing ones via apk.

Signature:

need_tool()

Parameters:

  • $@ - Tool names (space-separated)

Returns: 0 on success, 1 if installation failed

Behavior:

# Checks each tool
# If any missing: runs apk add for all
# Displays message before and after

Error Handling:

  • Returns 1 if apk add fails
  • Shows which tools failed
  • Suggests checking package names

Usage Examples:

# Example 1: Ensure common tools available
need_tool curl jq unzip git
# Installs any missing packages

# Example 2: Optional tool check
if need_tool myapp-cli; then
  myapp-cli --version
else
  echo "myapp-cli not available in apk"
fi

# Example 3: With error handling
need_tool docker || {
  echo "Failed to install docker"
  exit 1
}

net_resolves()

Purpose: Checks if hostname resolves and responds (Alpine-friendly DNS test).

Signature:

net_resolves()

Parameters:

  • $1 - Hostname to test

Returns: 0 if resolves and responds, 1 if fails

Behavior:

# Alpine doesn't have getent by default
# Falls back to nslookup if ping fails
# Returns success if either works

ping -c1 -W1 "$host" >/dev/null 2>&1 || nslookup "$host" >/dev/null 2>&1

Usage Examples:

# Example 1: Test GitHub connectivity
if net_resolves api.github.com; then
  echo "Can reach GitHub API"
else
  echo "GitHub API unreachable"
fi

# Example 2: In download function
net_resolves download.example.com || {
  echo "Download server not reachable"
  exit 1
}

ensure_usr_local_bin_persist()

Purpose: Ensures /usr/local/bin is in PATH across all shell sessions.

Signature:

ensure_usr_local_bin_persist()

Parameters: None

Returns: No explicit return value (modifies system)

Behavior:

# Creates /etc/profile.d/10-localbin.sh
# Script adds /usr/local/bin to PATH if not already present
# Runs on every shell startup

# Alpine uses /etc/profile for login shells
# profile.d scripts sourced automatically

Implementation:

PROFILE_FILE="/etc/profile.d/10-localbin.sh"
if [ ! -f "$PROFILE_FILE" ]; then
  echo 'case ":$PATH:" in *:/usr/local/bin:*) ;; *) export PATH="/usr/local/bin:$PATH";; esac' > "$PROFILE_FILE"
  chmod +x "$PROFILE_FILE"
fi

Usage Examples:

# Example 1: Make sure local tools available
ensure_usr_local_bin_persist
# Now /usr/local/bin binaries always in PATH

# Example 2: After installing custom tool
cp ./my-tool /usr/local/bin/
ensure_usr_local_bin_persist
# Tool immediately accessible in PATH

download_with_progress()

Purpose: Downloads file with progress bar (if pv available) or simple # progress.

Signature:

download_with_progress()

Parameters:

  • $1 - URL to download
  • $2 - Destination file path

Returns: 0 on success, 1 on failure

Behavior:

# Attempts to get content-length header
# If available: pipes through pv for progress bar
# If not: uses curl's built-in # progress
# Shows errors clearly

Requirements:

  • curl - For downloading
  • pv - Optional, for progress bar
  • Destination directory must exist

Usage Examples:

# Example 1: Simple download
download_with_progress "https://example.com/file.tar.gz" "/tmp/file.tar.gz"
# Shows progress bar if pv available

# Example 2: With error handling
if download_with_progress "$URL" "$DEST"; then
  echo "Downloaded successfully"
  tar -xzf "$DEST"
else
  echo "Download failed"
  exit 1
fi

GitHub Release Functions

check_for_gh_release()

Purpose: Checks GitHub releases for available updates and compares with currently installed version.

Signature:

check_for_gh_release()

Parameters:

  • $1 - Application name (e.g., "nodejs")
  • $2 - GitHub repository (e.g., "nodejs/node")
  • $3 - Pinned version (optional, e.g., "20.0.0")

Returns: 0 if update needed, 1 if current or pinned

Environment Variables Set:

  • CHECK_UPDATE_RELEASE - Latest available version (without v prefix)

Behavior:

# 1. Check network to api.github.com
# 2. Fetch latest release tag via GitHub API
# 3. Compare with installed version (stored in ~/.appname)
# 4. Show appropriate message:
#    - "app pinned to vX.X.X (no update)"
#    - "app pinned vX.X.X (upstream vY.Y.Y) → update/downgrade"
#    - "Update available: vA.A.A → vB.B.B"
#    - "Already up to date"

File Storage:

~/.${app_lc}  # File contains current version string
# Example: ~/.nodejs contains "20.10.0"

Usage Examples:

# Example 1: Check for update
check_for_gh_release "nodejs" "nodejs/node"
# Output: "Update available: v18.0.0 → v20.10.0"
# Sets: CHECK_UPDATE_RELEASE="20.10.0"

# Example 2: Pinned version (no update)
check_for_gh_release "nodejs" "nodejs/node" "20.0.0"
# Output: "app pinned to v20.0.0 (no update)"
# Returns 1 (no update available)

# Example 3: With error handling
if check_for_gh_release "myapp" "owner/myapp"; then
  echo "Update available: $CHECK_UPDATE_RELEASE"
  download_and_install
fi

Tool Installation Patterns

Pattern 1: Simple Package Installation

#!/bin/sh
need_tool curl jq  # Ensure tools available
# Continue with script

Pattern 2: GitHub Release Installation

#!/bin/sh
source <(curl -fsSL .../alpine-tools.func)
load_functions

# Check for updates
check_for_gh_release "myapp" "owner/myapp"

# Download from GitHub releases
RELEASE="$CHECK_UPDATE_RELEASE"
URL="https://github.com/owner/myapp/releases/download/v${RELEASE}/myapp-alpine.tar.gz"

download_with_progress "$URL" "/tmp/myapp-${RELEASE}.tar.gz"
tar -xzf "/tmp/myapp-${RELEASE}.tar.gz" -C /usr/local/bin/

Pattern 3: Version Pinning

#!/bin/sh
# For specific use case, pin to known good version
check_for_gh_release "nodejs" "nodejs/node" "20.10.0"
# Will use 20.10.0 even if 21.0.0 available

Package Management

Alpine Package Naming

Alpine packages often have different names than Debian:

Tool Alpine Debian
curl curl curl
Git git git
Docker docker docker.io
PostgreSQL postgresql-client postgresql-client
Build tools build-base build-essential
Development headers -dev packages -dev packages

Finding Alpine Packages

# Search for package
apk search myapp

# Show package info
apk info -d myapp

# List available versions
apk search myapp --all

Installing Alpine Packages

# Basic install (not cached)
apk add curl git

# Install with --no-cache (for containers)
apk add --no-cache curl git

# Force broken packages (last resort)
apk add --no-cache --force-broken-world util-linux

Best Practices

1. Use --no-cache in Containers

# Good: Saves space in container
apk add --no-cache curl git

# Avoid: Wastes space
apk update && apk add curl git

2. Check Tools Before Using

# Good: Graceful error
if ! has jq; then
  need_tool jq || exit 1
fi

# Using jq safely
jq . < input.json

3. Use need_tool() for Multiple

# Good: Install all at once
need_tool curl jq git unzip

# Less efficient: Individual checks
has curl || apk add curl
has jq || apk add jq

4. Ensure Persistence

# For custom tools in /usr/local/bin
ensure_usr_local_bin_persist

# Now available in all future shells
/usr/local/bin/my-custom-tool

5. Handle Network Failures

# Alpine often in isolated environments
if ! net_resolves api.github.com; then
  echo "GitHub API unreachable"
  # Fallback to local package or error
  exit 1
fi

Debugging

Check Package Availability

# List all available packages
apk search --all

# Find package by keyword
apk search curl

# Get specific package info
apk info postgresql-client

Verify Installation

# Check if tool installed
apk info | grep myapp

# Verify PATH
which curl
echo $PATH

Network Testing

# Test DNS
nslookup api.github.com

# Test connectivity
ping -c1 1.1.1.1

# Test download
curl -I https://api.github.com

Contributing

Adding New Helper Functions

When adding Alpine-specific helpers:

  1. Use POSIX shell (ash-compatible)
  2. Avoid bash-isms
  3. Include error handling
  4. Document with examples
  5. Test on actual Alpine container

Improving Package Installation

New patterns could support:

  • Automatic Alpine version detection
  • Package version pinning
  • Dependency resolution
  • Conflict detection

Notes

  • Alpine uses ash shell (POSIX-compatible, not bash)
  • Alpine apk is fast and has minimal overhead
  • Alpine containers ~5MB base image (vs 100MB+ for Debian)
  • No getent available by default (use nslookup fallback)
  • GitHub releases can be pre-compiled for Alpine musl

Clone this wiki locally