#!/usr/bin/env bash # Multica installer — installs or upgrades the Multica CLI. # # Install / upgrade CLI: # curl -fsSL https://work.vibesolutions.vn/releases/install.sh | bash # # After installation, run `multica setup` to configure your environment. # set -euo pipefail # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- # Every download comes from this release host. Override with the environment # variable of the same name (e.g. for a mirror or a test server). MULTICA_RELEASES_URL="${MULTICA_RELEASES_URL:-https://work.vibesolutions.vn/releases}" MULTICA_RELEASES_URL="${MULTICA_RELEASES_URL%/}" # Web app that serves the releases (used for user-facing links). MULTICA_APP_URL="${MULTICA_RELEASES_URL%/releases}" # Colors (disabled when not a terminal) if [ -t 1 ] || [ -t 2 ]; then BOLD='\033[1m' GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' CYAN='\033[0;36m' RESET='\033[0m' else BOLD='' GREEN='' YELLOW='' RED='' CYAN='' RESET='' fi # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- info() { printf "${BOLD}${CYAN}==> %s${RESET}\n" "$*"; } ok() { printf "${BOLD}${GREEN}✓ %s${RESET}\n" "$*"; } warn() { printf "${BOLD}${YELLOW}⚠ %s${RESET}\n" "$*" >&2; } fail() { printf "${BOLD}${RED}✗ %s${RESET}\n" "$*" >&2; exit 1; } command_exists() { command -v "$1" >/dev/null 2>&1; } running_in_ssh_session() { [ -n "${SSH_CONNECTION:-}" ] || [ -n "${SSH_CLIENT:-}" ] || [ -n "${SSH_TTY:-}" ] } print_remote_server_token_hint() { if ! running_in_ssh_session; then return fi printf " ${BOLD}Looks like a remote/SSH session.${RESET} Browser login may not be able to call back to this machine's localhost.\n" printf " Token login is usually simpler here:\n" printf " 1. On your local computer, open ${CYAN}%s/settings?tab=tokens${RESET}\n" "$MULTICA_APP_URL" printf " and create a token under ${BOLD}Settings > API Tokens${RESET}.\n" printf " 2. On this server, run:\n" printf " ${CYAN}multica login --token ${RESET}\n" printf " ${CYAN}multica daemon start${RESET}\n" printf "\n" } detect_os() { case "$(uname -s)" in Darwin) OS="darwin" ;; Linux) OS="linux" ;; MINGW*|MSYS*|CYGWIN*) fail "This script does not support Windows. Use the PowerShell installer instead: irm $MULTICA_RELEASES_URL/install.ps1 | iex" ;; *) fail "Unsupported operating system: $(uname -s). Multica supports macOS, Linux, and Windows." ;; esac ARCH="$(uname -m)" case "$ARCH" in x86_64) ARCH="amd64" ;; aarch64) ARCH="arm64" ;; arm64) ARCH="arm64" ;; *) fail "Unsupported architecture: $ARCH" ;; esac } # --------------------------------------------------------------------------- # CLI Installation # --------------------------------------------------------------------------- # Latest release tag (e.g. v0.6.0-vibe.1), or empty when it cannot be read. # MULTICA_VERSION pins a specific tag instead. get_latest_version() { if [ -n "${MULTICA_VERSION:-}" ]; then printf '%s' "$MULTICA_VERSION" return fi # head/tr keep only the first line; `|| true` avoids triggering pipefail curl -fsSL "$MULTICA_RELEASES_URL/latest.txt" 2>/dev/null | head -n 1 | tr -d '\r\n\t ' || true } # Prints the SHA-256 of a file, or nothing when no hashing tool is available. sha256_of() { if command_exists sha256sum; then sha256sum "$1" | awk '{print $1}' elif command_exists shasum; then shasum -a 256 "$1" | awk '{print $1}' fi } # Verifies $1 (downloaded file) against the checksums.txt entry for asset $2. verify_checksum() { local file="$1" asset="$2" base_url="$3" tmp_dir="$4" local checksums="$tmp_dir/checksums.txt" if ! curl -fsSL "$base_url/checksums.txt" -o "$checksums" 2>/dev/null; then warn "Could not download checksums.txt - skipping verification." return fi local expected expected=$(awk -v a="$asset" '{ name = $2; sub(/^\*/, "", name); if (name == a) { print tolower($1); exit } }' "$checksums") if [ -z "$expected" ]; then warn "Could not find checksum entry for $asset - skipping verification." return fi local actual actual=$(sha256_of "$file" | tr 'A-F' 'a-f') if [ -z "$actual" ]; then warn "Neither sha256sum nor shasum is available - skipping verification." return fi if [ "$actual" != "$expected" ]; then rm -rf "$tmp_dir" fail "Checksum verification failed. Expected: $expected, Got: $actual" fi ok "Checksum verified" } install_cli_binary() { info "Installing Multica CLI from $MULTICA_RELEASES_URL ..." local latest latest=$(get_latest_version) if [ -z "$latest" ]; then fail "Could not determine latest release from $MULTICA_RELEASES_URL/latest.txt. Check your network connection." fi local version="${latest#v}" local asset="multica-cli-${version}-${OS}-${ARCH}.tar.gz" local base_url="$MULTICA_RELEASES_URL/download/${latest}" local url="$base_url/$asset" local tmp_dir tmp_dir=$(mktemp -d) info "Downloading $url ..." if ! curl -fsSL "$url" -o "$tmp_dir/multica.tar.gz"; then rm -rf "$tmp_dir" fail "Failed to download CLI binary." fi verify_checksum "$tmp_dir/multica.tar.gz" "$asset" "$base_url" "$tmp_dir" tar -xzf "$tmp_dir/multica.tar.gz" -C "$tmp_dir" multica # Don't rely on the archive's file mode: archives packed on Windows carry 0644. chmod +x "$tmp_dir/multica" # Try /usr/local/bin first, fall back to ~/.local/bin. Tests and scripted # installs can override the first choice with MULTICA_BIN_DIR. local bin_dir="${MULTICA_BIN_DIR:-/usr/local/bin}" if [ -w "$bin_dir" ]; then mv "$tmp_dir/multica" "$bin_dir/multica" elif command_exists sudo; then sudo mv "$tmp_dir/multica" "$bin_dir/multica" else bin_dir="$HOME/.local/bin" mkdir -p "$bin_dir" mv "$tmp_dir/multica" "$bin_dir/multica" chmod +x "$bin_dir/multica" fi # Add to PATH if not already there if ! echo "$PATH" | tr ':' '\n' | grep -q "^$bin_dir$"; then export PATH="$bin_dir:$PATH" add_to_path "$bin_dir" warn "$bin_dir was not on your PATH; added it to ~/.bashrc / ~/.zshrc. Restart your shell or run: export PATH=\"$bin_dir:\$PATH\"" fi rm -rf "$tmp_dir" ok "Multica CLI installed to $bin_dir/multica" } add_to_path() { local dir="$1" local line="export PATH=\"$dir:\$PATH\"" for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do if [ -f "$rc" ] && ! grep -qF "$dir" "$rc"; then printf '\n# Added by Multica installer\n%s\n' "$line" >> "$rc" fi done } install_cli() { if command_exists multica; then local current_ver # `multica version` outputs "multica 0.3.23 (commit: f46b929eb, built: 2026-06-16T10:11:56Z)" — extract just the version current_ver=$(multica version 2>/dev/null | awk 'NR==1{print $2}' || echo "unknown") local latest_ver latest_ver=$(get_latest_version) # Normalize: strip leading 'v' for comparison local current_cmp="${current_ver#v}" local latest_cmp="${latest_ver#v}" if [ -z "$latest_ver" ] || [ "$current_cmp" = "$latest_cmp" ]; then ok "Multica CLI is up to date ($current_ver)" return 0 fi info "Multica CLI $current_ver installed, latest is $latest_ver — upgrading..." install_cli_binary local new_ver new_ver=$(multica version 2>/dev/null | awk 'NR==1{print $2}' || echo "unknown") ok "Multica CLI upgraded ($current_ver → $new_ver)" return 0 fi install_cli_binary # Verify if ! command_exists multica; then fail "CLI installed but 'multica' not found on PATH. You may need to restart your shell." fi } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- run_default() { printf "\n" printf "${BOLD} Multica — Installer${RESET}\n" printf "\n" detect_os install_cli printf "\n" printf "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n" printf "${BOLD}${GREEN} ✓ Multica CLI is ready!${RESET}\n" printf "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n" printf "\n" printf " ${BOLD}Next: configure your environment${RESET}\n" printf "\n" printf " ${CYAN}multica setup${RESET} # Connect to %s\n" "$MULTICA_APP_URL" printf "\n" print_remote_server_token_hint } # --------------------------------------------------------------------------- # Entry point # --------------------------------------------------------------------------- main() { while [ $# -gt 0 ]; do case "$1" in --with-server|--local|--stop) fail "$1 is not supported: the Multica server is installed and managed by your administrator. Run this script without options to install the CLI." ;; --help|-h) echo "Usage: install.sh" echo "" echo " Install / upgrade the Multica CLI from $MULTICA_RELEASES_URL" echo "" echo "Environment variables:" echo " MULTICA_RELEASES_URL Release host to download from" echo " (default: https://work.vibesolutions.vn/releases)" echo " MULTICA_VERSION Release tag to install (default: latest)" echo " MULTICA_BIN_DIR Target directory for the CLI binary" echo " (default: /usr/local/bin, then \$HOME/.local/bin)" echo "" echo "After installation, run 'multica setup' to configure your environment." exit 0 ;; *) warn "Unknown option: $1" ;; esac shift done run_default } main "$@"