diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9c8b2f5 --- /dev/null +++ b/install.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env sh +# covecto installer — https://github.com/codecoradev/covecto +# Usage: curl -fsSL https://codecora.dev/covecto/install | sh + +set -e + +REPO="codecoradev/covecto" +BINARY_NAME="covecto" +INSTALL_DIR="${COVECTO_INSTALL_DIR:-$HOME/.local/bin}" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +info() { + printf "${GREEN}[INFO]${NC} %s\n" "$1" +} + +warn() { + printf "${YELLOW}[WARN]${NC} %s\n" "$1" +} + +error() { + printf "${RED}[ERROR]${NC} %s\n" "$1" + exit 1 +} + +# Detect OS +detect_os() { + case "$(uname -s)" in + Linux*) OS="linux";; + Darwin*) OS="darwin";; + *) error "Unsupported operating system: $(uname -s)";; + esac +} + +# Detect architecture +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) ARCH="x86_64";; + arm64|aarch64) ARCH="aarch64";; + *) error "Unsupported architecture: $(uname -m)";; + esac +} + +# Get latest release version +get_latest_version() { + VERSION=$(curl -sI "https://github.com/${REPO}/releases/latest" \ + | grep -i '^location:' \ + | sed -E 's|.*/tag/([^[:space:]]+).*|\1|' \ + | tr -d '\r') + + if [ -z "$VERSION" ]; then + warn "Redirect lookup failed, falling back to GitHub API..." + VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ + | grep '"tag_name":' \ + | sed -E 's/.*"([^"]+)".*/\1/') + fi + + if [ -z "$VERSION" ]; then + error "Failed to get latest version. Set COVECTO_VERSION=vX.Y.Z to pin a version." + fi +} + +# Build target triple +get_target() { + case "$OS" in + linux) + case "$ARCH" in + x86_64) TARGET="x86_64-unknown-linux-gnu";; + aarch64) TARGET="aarch64-unknown-linux-gnu";; + esac + ;; + darwin) + if [ "$ARCH" != "aarch64" ]; then + warn "No pre-built binary for x86_64 macOS. Install via cargo:" + warn " cargo install --path crates/covecto-cli" + exit 0 + fi + TARGET="aarch64-apple-darwin" + ;; + esac +} + +# Download and install +install() { + info "Detected: $OS $ARCH" + info "Target: $TARGET" + info "Version: $VERSION" + + ARCHIVE_NAME="${BINARY_NAME}-${TARGET}-${VERSION}.tar.gz" + DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ARCHIVE_NAME}" + TEMP_DIR=$(mktemp -d) + ARCHIVE="${TEMP_DIR}/${ARCHIVE_NAME}" + + CHECKSUMS_URL="https://github.com/${REPO}/releases/download/${VERSION}/checksums-sha256.txt" + CHECKSUM_FILE="${TEMP_DIR}/checksums-sha256.txt" + + info "Downloading from: $DOWNLOAD_URL" + if ! curl -fsSL "$DOWNLOAD_URL" -o "$ARCHIVE"; then + error "Failed to download ${ARCHIVE_NAME}" + fi + + # Verify SHA256 checksum + info "Downloading checksums..." + if curl -fsSL "$CHECKSUMS_URL" -o "$CHECKSUM_FILE"; then + info "Verifying SHA256 checksum..." + EXPECTED=$(grep -F "$ARCHIVE_NAME" "$CHECKSUM_FILE" | awk '{print $1}') + if [ -n "$EXPECTED" ]; then + ACTUAL=$(sha256sum "$ARCHIVE" | awk '{print $1}') + if [ "$ACTUAL" != "$EXPECTED" ]; then + error "Checksum mismatch! Expected: ${EXPECTED}, got: ${ACTUAL}" + fi + info "Checksum verified: $EXPECTED" + else + warn "Checksum for ${ARCHIVE_NAME} not found — skipping verification" + fi + else + warn "Failed to download checksums — skipping verification" + fi + + # Verify archive integrity (CWE-22) + info "Verifying archive integrity..." + if tar -tzf "$ARCHIVE" | grep -qE '^/|(^|/)\.\.(/|$)'; then + error "Archive contains unsafe paths — refusing to extract" + fi + + info "Extracting..." + tar -xzf "$ARCHIVE" -C "$TEMP_DIR" + + mkdir -p "$INSTALL_DIR" + mv "${TEMP_DIR}/${BINARY_NAME}" "${INSTALL_DIR}/" + chmod +x "${INSTALL_DIR}/${BINARY_NAME}" + + rm -rf "$TEMP_DIR" + + info "Successfully installed ${BINARY_NAME} to ${INSTALL_DIR}/${BINARY_NAME}" +} + +# Verify +verify() { + if command -v "$BINARY_NAME" >/dev/null 2>&1; then + INSTALLED_VERSION=$("$BINARY_NAME" --version 2>/dev/null || echo "unknown") + info "Verification: $INSTALLED_VERSION" + else + warn "Binary installed but not in PATH. Add to your shell profile:" + case "${SHELL:-}" in + */zsh) + warn ' echo '\''export PATH="$HOME/.local/bin:$PATH"'\'' >> ~/.zshrc' + ;; + */bash) + warn ' echo '\''export PATH="$HOME/.local/bin:$PATH"'\'' >> ~/.bashrc' + ;; + */fish) + warn ' fish_add_path ~/.local/bin' + ;; + *) + warn ' export PATH="$HOME/.local/bin:$PATH"' + ;; + esac + fi +} + +main() { + info "Installing ${BINARY_NAME}..." + detect_os + detect_arch + get_target + if [ -n "$COVECTO_VERSION" ]; then + VERSION="$COVECTO_VERSION" + info "Using pinned version: $VERSION" + else + get_latest_version + fi + install + verify + echo "" + info "Done! Run '${BINARY_NAME} --help' to get started." +} + +main \ No newline at end of file