#!/usr/bin/env sh # ccmux installer # Usage: curl -fsSL https://ccmux.com/install | sh set -e RELEASE_REPO="Highwall2016/homebrew-tap" GITHUB_API="https://api.github.com/repos/${RELEASE_REPO}/releases/latest" INSTALL_DIR="${CCMUX_INSTALL_DIR:-/usr/local/bin}" red() { printf '\033[31m%s\033[0m\n' "$*"; } green() { printf '\033[32m%s\033[0m\n' "$*"; } cyan() { printf '\033[36m%s\033[0m\n' "$*"; } bold() { printf '\033[1m%s\033[0m\n' "$*"; } detect_os() { case "$(uname -s 2>/dev/null)" in Darwin) echo "darwin" ;; Linux) echo "linux" ;; MINGW*|MSYS*|CYGWIN*) echo "windows_shell" ;; *) echo "unknown" ;; esac } detect_arch() { case "$(uname -m 2>/dev/null)" in x86_64|amd64) echo "amd64" ;; arm64|aarch64) echo "arm64" ;; *) echo "unsupported" ;; esac } is_wsl() { [ -f /proc/version ] && grep -qi microsoft /proc/version 2>/dev/null } # ── Windows (Git Bash / MSYS2 / Cygwin) ──────────────────────────────────── OS=$(detect_os) if [ "$OS" = "windows_shell" ]; then bold "Windows (Git Bash / MSYS2) detected — installing ccmux.exe..." printf '\n' printf '(PowerShell users can also run: iwr -useb https://ccmux.highwall-lab.us.ci/install.ps1 | iex)\n\n' ARCH=$(detect_arch) if [ "$ARCH" = "unsupported" ]; then red "Unsupported architecture: $(uname -m 2>/dev/null || echo '?')" exit 1 fi printf 'Fetching latest release...\n' if command -v curl >/dev/null 2>&1; then RELEASE_JSON=$(curl -fsSL "https://api.github.com/repos/${RELEASE_REPO}/releases/latest") elif command -v wget >/dev/null 2>&1; then RELEASE_JSON=$(wget -qO- "https://api.github.com/repos/${RELEASE_REPO}/releases/latest") else red "curl or wget is required." exit 1 fi VERSION=$(printf '%s' "${RELEASE_JSON}" \ | grep '"tag_name"' | grep -o '"[^"]*"' | tail -1 \ | tr -d '"' | sed 's/^v//') if [ -z "$VERSION" ]; then red "Failed to parse latest version." exit 1 fi ZIPFILE="ccmux-${VERSION}-windows-${ARCH}.zip" ZIPURL=$(printf '%s' "${RELEASE_JSON}" \ | awk -v asset="${ZIPFILE}" ' $0 ~ "\"name\": \"" asset "\"" { found=1 } found && /"browser_download_url":/ { sub(/^.*"browser_download_url": "/, "") sub(/".*$/, "") print exit }') if [ -z "${ZIPURL}" ]; then red "Release v${VERSION} does not include a Windows/${ARCH} build." printf 'Available assets:\n' printf '%s' "${RELEASE_JSON}" \ | awk '/"name": "ccmux-/ { sub(/^.*"name": "/, " - "); sub(/".*$/, ""); print }' printf '\nThe release needs this asset uploaded:\n %s\n' "${ZIPFILE}" exit 1 fi cyan " Version: ${VERSION}" printf 'Downloading %s...\n' "${ZIPFILE}" _wwork=$(mktemp -d) trap 'rm -rf "${_wwork}"' EXIT INT TERM ZIPPATH="${_wwork}/${ZIPFILE}" if command -v curl >/dev/null 2>&1; then curl -fsSL --progress-bar "${ZIPURL}" -o "${ZIPPATH}" || { red "Download failed: ${ZIPURL}"; exit 1; } else wget -q --show-progress "${ZIPURL}" -O "${ZIPPATH}" || { red "Download failed: ${ZIPURL}"; exit 1; } fi if ! command -v unzip >/dev/null 2>&1; then red "unzip not found. Install it via your package manager or use the PowerShell installer." exit 1 fi unzip -q "${ZIPPATH}" -d "${_wwork}" WIN_INSTALL_DIR="${LOCALAPPDATA}/Programs/ccmux" WIN_INSTALL_DIR_UNIX=$(cygpath -u "${WIN_INSTALL_DIR}" 2>/dev/null || echo "${_wwork}/install") mkdir -p "${WIN_INSTALL_DIR_UNIX}" cp "${_wwork}/ccmux.exe" "${WIN_INSTALL_DIR_UNIX}/ccmux.exe" cp "${_wwork}/ccmux-agent.exe" "${WIN_INSTALL_DIR_UNIX}/ccmux-agent.exe" printf '\n' green "ccmux ${VERSION} installed to ${WIN_INSTALL_DIR}" printf '\n' printf 'Add it to your PATH in System Settings, or run:\n' printf ' export PATH="%s:$PATH"\n' "${WIN_INSTALL_DIR_UNIX}" printf '\n' bold 'Next steps:' printf ' 1. ccmux auth login\n' printf ' 2. Open the ccmux app on your phone\n' exit 0 fi if [ "$OS" = "unknown" ]; then red "Unsupported OS: $(uname -s 2>/dev/null || echo '?')" exit 1 fi ARCH=$(detect_arch) if [ "$ARCH" = "unsupported" ]; then red "Unsupported architecture: $(uname -m 2>/dev/null || echo '?')" exit 1 fi if is_wsl; then printf 'Detected: WSL (Linux on Windows)\n' fi bold "Installing ccmux..." cyan " OS: ${OS}" cyan " Arch: ${ARCH}" # ── Resolve latest version ────────────────────────────────────────────────── printf 'Fetching latest release...\n' if command -v curl >/dev/null 2>&1; then RELEASE_JSON=$(curl -fsSL "${GITHUB_API}") elif command -v wget >/dev/null 2>&1; then RELEASE_JSON=$(wget -qO- "${GITHUB_API}") else red "curl or wget is required but neither was found." exit 1 fi # Extract tag_name value; handles both "v0.1.2" and "0.1.2" tags. VERSION=$(printf '%s' "${RELEASE_JSON}" \ | grep '"tag_name"' \ | grep -o '"[^"]*"' | tail -1 \ | tr -d '"' | sed 's/^v//') if [ -z "$VERSION" ]; then red "Failed to parse latest version from GitHub API." exit 1 fi TARBALL="ccmux-${VERSION}-${OS}-${ARCH}.tar.gz" URL="https://github.com/${RELEASE_REPO}/releases/download/v${VERSION}/${TARBALL}" cyan " Version: ${VERSION}" printf 'Downloading %s...\n' "${TARBALL}" # ── Download ──────────────────────────────────────────────────────────────── _work=$(mktemp -d) trap 'rm -rf "${_work}"' EXIT INT TERM TARPATH="${_work}/${TARBALL}" if command -v curl >/dev/null 2>&1; then curl -fsSL --progress-bar "${URL}" -o "${TARPATH}" || { red "Download failed: ${URL}" exit 1 } else wget -q --show-progress "${URL}" -O "${TARPATH}" || { red "Download failed: ${URL}" exit 1 } fi if [ ! -s "${TARPATH}" ]; then red "Downloaded file is empty — the release may not include a ${OS}/${ARCH} build." exit 1 fi tar -xzf "${TARPATH}" -C "${_work}" for bin in ccmux ccmux-agent; do if [ ! -f "${_work}/${bin}" ]; then red "Expected binary '${bin}' not found in tarball." exit 1 fi done # ── Install ───────────────────────────────────────────────────────────────── NEED_SUDO=0 if [ ! -d "${INSTALL_DIR}" ] || [ ! -w "${INSTALL_DIR}" ]; then if [ "$(id -u)" -ne 0 ]; then NEED_SUDO=1 printf 'No write access to %s — will use sudo.\n' "${INSTALL_DIR}" sudo mkdir -p "${INSTALL_DIR}" else mkdir -p "${INSTALL_DIR}" fi fi copy_bin() { local src="${_work}/$1" local dst="${INSTALL_DIR}/$1" if [ "$NEED_SUDO" = "1" ]; then sudo cp "${src}" "${dst}" sudo chmod 755 "${dst}" else cp "${src}" "${dst}" chmod 755 "${dst}" fi } copy_bin ccmux copy_bin ccmux-agent # macOS: strip Gatekeeper quarantine so binaries run without a security prompt. if [ "$OS" = "darwin" ]; then xattr -d com.apple.quarantine "${INSTALL_DIR}/ccmux" 2>/dev/null || true xattr -d com.apple.quarantine "${INSTALL_DIR}/ccmux-agent" 2>/dev/null || true fi # ── Post-install ───────────────────────────────────────────────────────────── printf '\n' green "ccmux ${VERSION} installed successfully!" printf '\n' if ! command -v ccmux >/dev/null 2>&1; then printf 'Installed to %s, but it is not on your PATH.\n' "${INSTALL_DIR}" printf 'Add this to your shell config (~/.zshrc, ~/.bashrc, etc.):\n' printf '\n' printf ' export PATH="%s:$PATH"\n' "${INSTALL_DIR}" printf '\n' printf 'Then restart your shell or run: source ~/.zshrc\n' printf '\n' fi bold 'Next steps:' printf ' 1. Start the agent: ccmux-agent\n' printf ' 2. Login: ccmux auth login\n' printf ' 3. Open the ccmux app on your phone and connect\n' printf '\n'