#!/usr/bin/env bash # Install the official Linux CLI to /usr/local/bin without a Rust toolchain. # Keep all work inside main so an incomplete piped download cannot start installation. set -euo pipefail main() { local version=${1:-latest} repository=${WASMD_RELEASE_REPOSITORY:-liusha-com/wasmd} local platform architecture target archive base resolved expected actual member listing local install_dir bin_dir work staged profile path_line shell_name installed_version bin_staged='' local -a privileged=() if [[ $version == --help || $version == -h ]]; then cat <<'HELP' Usage: install.sh [vMAJOR.MINOR.PATCH] Installs the latest stable Wasmd release, or the specified release tag. Linux x86_64 and aarch64 are supported, including musl-based distributions. WASMD_INSTALL Installation root (default: /usr/local; binary goes in bin/) WASMD_NO_MODIFY_PATH=1 Do not edit shell startup files WASMD_RELEASE_REPOSITORY Release repository (default: liusha-com/wasmd) HELP return fi fail() { printf 'wasmd install: %s\n' "$*" >&2; exit 1; } [[ $# -le 1 ]] || fail 'Expected at most one release version. Use --help.' [[ $repository =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || fail 'Invalid release repository.' [[ $version == latest || $version =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][A-Za-z0-9.-]+)?$ ]] || fail 'Use a release tag such as v0.1.0.' for tool in curl tar sha256sum uname mktemp mkdir chmod mv rm grep awk install; do command -v "$tool" >/dev/null 2>&1 || fail "Required command not found: $tool" done platform=$(uname -s) [[ $platform == Linux ]] || fail "This installer supports Linux; detected $platform. See https://wasmd.com/docs/getting-started/ for other platforms." architecture=$(uname -m) case "$architecture" in x86_64 | amd64) target=x86_64-unknown-linux-musl ;; aarch64 | arm64) target=aarch64-unknown-linux-musl ;; *) fail "Unsupported Linux architecture: $architecture (supported: x86_64, aarch64)." ;; esac install_dir=${WASMD_INSTALL:-/usr/local} [[ $install_dir == /* && $install_dir != *$'\n'* && $install_dir != *$'\r'* ]] || fail 'WASMD_INSTALL must be an absolute path without newlines.' bin_dir=${install_dir%/}/bin base="https://github.com/$repository/releases" # Resolve latest once so the archive and checksum manifest use the same release. if [[ $version == latest ]]; then resolved=$(curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSLI \ --connect-timeout 15 --max-time 60 --retry 2 --output /dev/null \ --write-out '%{url_effective}' "$base/latest") || fail "Cannot find a public release in $repository. Check your network or specify a published version." [[ $resolved == "$base/tag/"* ]] || fail 'GitHub did not resolve a release tag.' version=${resolved#"$base/tag/"} [[ $version =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][A-Za-z0-9.-]+)?$ ]] || fail 'Unexpected release tag returned by GitHub.' fi archive="wasmd-$target.tar.gz" work=$(mktemp -d) # The generated temporary directory is the only cleanup target. trap 'rm -rf -- "$work"; if [[ -n $bin_staged ]]; then "${privileged[@]}" rm -f -- "$bin_staged"; fi' EXIT trap 'exit 130' INT trap 'exit 143' TERM printf 'Installing Wasmd %s for %s…\n' "$version" "$target" for member in "$archive" SHA256SUMS; do curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSL \ --connect-timeout 15 --max-time 600 --retry 2 \ --output "$work/$member" "$base/download/$version/$member" \ || fail "Download failed: $member ($version). Your existing binary has not been changed." done expected=$(awk -v name="$archive" '$2 == name { print $1 }' "$work/SHA256SUMS") [[ $expected =~ ^[0-9a-fA-F]{64}$ ]] || fail "Missing, duplicate, or invalid SHA-256 entry for $archive." actual=$(sha256sum "$work/$archive") [[ ${actual%% *} == "${expected,,}" ]] || fail 'SHA-256 mismatch. Your existing binary has not been changed.' # Extract just the single regular binary to stdout. Never unpack arbitrary paths, # permissions, symlinks, or other archive members into the user's filesystem. listing=$(tar -tzf "$work/$archive") || fail 'Invalid release archive.' member=$(printf '%s\n' "$listing" | awk '$0 == "wasmd" || $0 == "./wasmd" { print }') [[ $member == wasmd || $member == ./wasmd ]] || fail 'Archive must contain exactly one wasmd binary.' listing=$(tar -tvzf "$work/$archive" -- "$member") || fail 'Cannot inspect release binary.' [[ $listing == -* ]] || fail 'Release binary must be a regular file.' staged="$work/wasmd" tar -xOzf "$work/$archive" -- "$member" > "$staged" || fail 'Cannot extract release binary.' chmod 755 "$staged" installed_version=$("$staged" --version) || fail 'The downloaded binary cannot run on this machine. Your existing binary has not been changed.' [[ $installed_version == "wasmd ${version#v}" ]] || fail "Binary version does not match release $version: $installed_version" [[ ! -d $bin_dir/wasmd ]] || fail "$bin_dir/wasmd is a directory." # Elevate only the final installation, after validating the downloaded binary. if ! mkdir -p -- "$bin_dir" 2>/dev/null || [[ ! -w $bin_dir ]]; then if [[ $EUID != 0 ]]; then command -v sudo >/dev/null 2>&1 || fail "Writing to $bin_dir requires root. Run as root or set WASMD_INSTALL to a writable prefix." sudo -v || fail 'Administrator authorization failed.' privileged=(sudo) fi fi "${privileged[@]}" mkdir -p -- "$bin_dir" || fail "Cannot create $bin_dir." bin_staged=$("${privileged[@]}" mktemp "$bin_dir/.wasmd-install.XXXXXXXX") || fail 'Cannot stage the installation.' "${privileged[@]}" install -m 755 -- "$staged" "$bin_staged" || fail 'Cannot stage the binary.' "${privileged[@]}" mv -fT -- "$bin_staged" "$bin_dir/wasmd" || fail 'Cannot replace the installed binary.' bin_staged='' printf '\nInstalled %s to %s/wasmd\n' "$installed_version" "$bin_dir" # Quote the concrete path, including spaces and shell metacharacters. Repeated # installs do not append duplicate lines. Never overwrite an existing profile. printf -v path_line 'export PATH=%q:"$PATH"' "$bin_dir" if [[ $bin_dir != /usr/local/bin && ${WASMD_NO_MODIFY_PATH:-0} != 1 && -n ${HOME:-} ]]; then shell_name=${SHELL:-bash} shell_name=${shell_name##*/} case "$shell_name" in bash | '') for profile in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do # A new .bash_profile would hide an existing .profile on login. [[ $profile != "$HOME/.bash_profile" || -f $profile ]] || continue if ! grep -Fqx -- "$path_line" "$profile" 2>/dev/null; then printf '\n# Wasmd\n%s\n' "$path_line" >> "$profile" || printf 'Could not update %s; add the PATH command below manually.\n' "$profile" >&2 fi done ;; zsh) profile="${ZDOTDIR:-$HOME}/.zshrc" if ! grep -Fqx -- "$path_line" "$profile" 2>/dev/null; then printf '\n# Wasmd\n%s\n' "$path_line" >> "$profile" || printf 'Could not update %s; add the PATH command below manually.\n' "$profile" >&2 fi ;; *) printf 'Add %s to the PATH in your shell configuration.\n' "$bin_dir" ;; esac fi printf '\nFor this terminal (Bash/Zsh), run:\n %s\n wasmd --help\n' "$path_line" printf '\nTo update, run the installer again.\n' rm -rf -- "$work" trap - EXIT INT TERM } main "$@"