#!/bin/sh
set -e
# Compute Portal CLI installer
# Usage: curl -fsSL https://install.computeportal.io | sh
#
# Environment variables:
# CP_INSTALL_DIR Install directory (default: /usr/local/bin, fallback: ~/.local/bin)
# CP_VERSION Pin a specific version (default: latest)
#
# Flags (via sh -s --):
# --install-dir
Install directory
# --version Pin a specific version
BASE_URL="${CP_BASE_URL:-https://install.computeportal.io}"
BINARY_NAME="cpctl"
# ── Parse flags ───────────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
case "$1" in
--install-dir) CP_INSTALL_DIR="$2"; shift 2 ;;
--version) CP_VERSION="$2"; shift 2 ;;
*) printf 'Unknown flag: %s\nUsage: curl -fsSL %s | sh -s -- [--install-dir DIR] [--version VER]\n' "$1" "${BASE_URL}" >&2; exit 1 ;;
esac
done
# ── Resolve install directory ─────────────────────────────────────────────────
if [ -n "${CP_INSTALL_DIR:-}" ]; then
INSTALL_DIR="${CP_INSTALL_DIR}"
elif [ -w "/usr/local/bin" ]; then
INSTALL_DIR="/usr/local/bin"
else
INSTALL_DIR="${HOME}/.local/bin"
fi
# ── Terminal colours ──────────────────────────────────────────────────────────
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
BOLD="$(tput bold 2>/dev/null || true)"
GREEN="$(tput setaf 2 2>/dev/null || true)"
YELLOW="$(tput setaf 3 2>/dev/null || true)"
RED="$(tput setaf 1 2>/dev/null || true)"
RESET="$(tput sgr0 2>/dev/null || true)"
else
BOLD="" GREEN="" YELLOW="" RED="" RESET=""
fi
log() { printf '%s==>%s %s\n' "${GREEN}${BOLD}" "${RESET}" "$*"; }
warn() { printf '%s==>%s %sWARNING:%s %s\n' "${YELLOW}${BOLD}" "${RESET}" "${YELLOW}" "${RESET}" "$*"; }
die() { printf '%s==>%s %sERROR:%s %s\n' "${RED}${BOLD}" "${RESET}" "${RED}" "${RESET}" "$*" >&2; exit 1; }
# ── Detect OS ─────────────────────────────────────────────────────────────────
OS="$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]')"
case "${OS}" in
linux) OS="linux" ;;
darwin) OS="darwin" ;;
*) die "Unsupported OS: ${OS}. Supported: linux, darwin." ;;
esac
# ── Detect architecture ───────────────────────────────────────────────────────
ARCH="$(uname -m 2>/dev/null)"
case "${ARCH}" in
x86_64|amd64) ARCH="amd64" ;;
aarch64|arm64|arm*) ARCH="arm64" ;;
*) die "Unsupported architecture: ${ARCH}. Supported: amd64, arm64." ;;
esac
# ── Detect download tool ──────────────────────────────────────────────────────
if command -v curl >/dev/null 2>&1; then
DOWNLOAD="curl -fsSL"
DOWNLOAD_TO="curl -fsSL -o"
elif command -v wget >/dev/null 2>&1; then
DOWNLOAD="wget -qO-"
DOWNLOAD_TO="wget -qO"
else
die "curl or wget is required to install the Compute Portal CLI."
fi
# ── Resolve version ───────────────────────────────────────────────────────────
if [ -n "${CP_VERSION:-}" ]; then
VERSION="${CP_VERSION}"
else
log "Fetching latest version..."
VERSION="$(${DOWNLOAD} "${BASE_URL}/version" 2>/dev/null | tr -d '[:space:]')"
[ -n "${VERSION}" ] || die "Could not fetch version from ${BASE_URL}/version"
fi
log "Installing Compute Portal CLI ${VERSION} (${OS}/${ARCH})..."
# ── Build URLs ────────────────────────────────────────────────────────────────
BINARY_ASSET="cpctl-${OS}-${ARCH}"
DOWNLOAD_URL="${BASE_URL}/releases/${VERSION}/${BINARY_ASSET}"
CHECKSUM_URL="${BASE_URL}/releases/${VERSION}/${BINARY_ASSET}.sha256"
# ── Ensure install dir exists and is writable ─────────────────────────────────
mkdir -p "${INSTALL_DIR}" 2>/dev/null || die "Cannot create install directory: ${INSTALL_DIR}"
[ -w "${INSTALL_DIR}" ] || die "Install directory is not writable: ${INSTALL_DIR}. Set CP_INSTALL_DIR to a writable path or use --install-dir."
BINARY_PATH="${INSTALL_DIR}/${BINARY_NAME}"
if [ "${INSTALL_DIR}" = "${HOME}/.local/bin" ] && [ -z "${CP_INSTALL_DIR:-}" ]; then
warn "/usr/local/bin is not writable — installing to ${INSTALL_DIR} instead."
warn "Add this to your shell profile: export PATH=\"${INSTALL_DIR}:\$PATH\""
fi
# ── Download binary ───────────────────────────────────────────────────────────
TMP_FILE="$(mktemp)"
trap 'rm -f "${TMP_FILE}"' EXIT INT TERM
log "Downloading ${DOWNLOAD_URL}..."
${DOWNLOAD_TO} "${TMP_FILE}" "${DOWNLOAD_URL}" || die "Download failed. Try: CP_VERSION= curl -fsSL ${BASE_URL} | sh"
# ── Verify checksum ───────────────────────────────────────────────────────────
EXPECTED="$(${DOWNLOAD} "${CHECKSUM_URL}" 2>/dev/null | awk '{print $1}' || true)"
if [ -n "${EXPECTED}" ]; then
log "Verifying checksum..."
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL="$(sha256sum "${TMP_FILE}" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
ACTUAL="$(shasum -a 256 "${TMP_FILE}" | awk '{print $1}')"
else
warn "sha256sum/shasum not found — skipping checksum verification."
ACTUAL="${EXPECTED}"
fi
[ "${ACTUAL}" = "${EXPECTED}" ] || die "Checksum mismatch. The download may be corrupt."
else
warn "Checksum not available — skipping verification."
fi
# ── Install ───────────────────────────────────────────────────────────────────
chmod +x "${TMP_FILE}"
log "Installing to ${BINARY_PATH}..."
mv "${TMP_FILE}" "${BINARY_PATH}"
# ── Verify ────────────────────────────────────────────────────────────────────
if ! command -v "${BINARY_NAME}" >/dev/null 2>&1; then
warn "${INSTALL_DIR} may not be in your PATH."
warn "Add this to your shell profile: export PATH=\"${INSTALL_DIR}:\$PATH\""
fi
INSTALLED_VERSION="$("${BINARY_PATH}" version 2>/dev/null || echo "unknown")"
printf '\n'
printf '%s✓%s Compute Portal CLI installed successfully!\n' "${GREEN}${BOLD}" "${RESET}"
printf ' Version : %s\n' "${INSTALLED_VERSION}"
printf ' Binary : %s\n' "${BINARY_PATH}"
printf '\n'
printf 'Get started:\n'
printf ' %scpctl login --token %s\n' "${BOLD}" "${RESET}"
printf ' %scpctl deploy --image nginx:latest%s\n' "${BOLD}" "${RESET}"
printf '\n'
printf 'Get your API token at: %shttps://computeportal.io/user/orders%s\n' "${BOLD}" "${RESET}"
printf 'Documentation: %shttps://docs.computeportal.io%s\n' "${BOLD}" "${RESET}"