#!/usr/bin/env bash # Orkestra CLI installer # # Usage: # curl -sSL https://get.orkestra.sh | bash # # Environment variables: # ORK_VERSION — pin a specific version tag (default: latest release) # ORK_INSTALL_DIR — install directory (default: ~/.orkestra/bin) # ORK_SKIP_CC — skip Control Center binary (default: false) # ORK_SKIP_COMPLETION — skip shell completion setup (default: false) # # 1. Add the binary name to BINARIES. # 2. If it should be skippable, add a case entry in component_skipped(). # That is the only change needed — download URL, checksum, and extraction # all follow the same convention automatically. # set -euo pipefail # Config REPO="orkspace/orkestra" INSTALL_DIR="${ORK_INSTALL_DIR:-${HOME}/.orkestra/bin}" VERSION="${ORK_VERSION:-}" SKIP_CC="${ORK_SKIP_CC:-false}" SKIP_COMPLETION="${ORK_SKIP_COMPLETION:-false}" # All Orkestra CLI binaries, in install order. # To add a new binary, append it here. BINARIES=("ork" "orkcc") # Colours RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' RESET='\033[0m' info() { echo -e "${BLUE}[orkestra]${RESET} $*"; } success() { echo -e "${GREEN}[orkestra]${RESET} $*"; } warn() { echo -e "${YELLOW}[orkestra]${RESET} $*"; } error() { echo -e "${RED}[orkestra]${RESET} $*" >&2; } fatal() { error "$*"; exit 1; } # Banner echo -e "${BOLD}" cat <<'EOF' ___ _ _ / _ \ _ _| |___ _ _ ___| |_ _ _ __ _ | (_) | || | / -_) ' \/ -_) _| '_/ _` | \___/ \_,_|_\___|_||_\___|\__|_| \__,_| O R K E S T R A EOF echo -e "${RESET}" # Dependencies check_deps() { for cmd in curl tar grep sed; do command -v "${cmd}" >/dev/null 2>&1 \ || fatal "Required command not found: ${cmd}" done } # Platform detection detect_platform() { local os arch case "$(uname -s)" in Linux) os="linux" ;; Darwin) os="darwin" ;; *) fatal "Unsupported OS: $(uname -s). Use WSL on Windows." ;; esac case "$(uname -m)" in x86_64) arch="amd64" ;; aarch64|arm64) arch="arm64" ;; *) fatal "Unsupported architecture: $(uname -m)" ;; esac echo "${os}_${arch}" } # Version resolution resolve_version() { if [[ -n "${VERSION}" ]]; then echo "${VERSION}" return fi info "Fetching latest version..." >&2 local tag tag=$(curl -sSf "https://api.github.com/repos/${REPO}/releases/latest" \ | grep '"tag_name"' \ | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') if [[ -z "${tag}" ]]; then fatal "Could not resolve latest version. Check your network or set ORK_VERSION explicitly." fi echo "${tag}" } # Component skip rules # Returns 0 (true) when the binary should be skipped. # Add a case entry here when a new binary needs a skip flag. component_skipped() { local binary="$1" case "${binary}" in orkcc) [[ "${SKIP_CC}" == "true" ]] ;; *) return 1 ;; esac } # Checksum verification # Uses -c (POSIX short form) which works on GNU coreutils, BusyBox, and macOS. # --check is GNU-only and fails on BusyBox (Alpine/CI containers). verify_checksum() { local archive="$1" local checksums_file="$2" if command -v sha256sum >/dev/null 2>&1; then grep "${archive}" "${checksums_file}" | sha256sum -c \ || fatal "Checksum verification failed for ${archive}" elif command -v shasum >/dev/null 2>&1; then grep "${archive}" "${checksums_file}" | shasum -a 256 -c \ || fatal "Checksum verification failed for ${archive}" else warn "No checksum tool found (sha256sum / shasum) — skipping verification" fi } # Single component installer install_component() { local binary="$1" local platform="$2" local version="$3" if component_skipped "${binary}"; then info "Skipping ${binary} (disabled by flag)" return 0 fi local archive="${binary}_${platform}.tar.gz" local download_url="https://github.com/${REPO}/releases/download/${version}/${archive}" local checksum_url="https://github.com/${REPO}/releases/download/${version}/checksums.txt" local tmp_dir tmp_dir=$(mktemp -d) # shellcheck disable=SC2064 trap "rm -rf '${tmp_dir}'" RETURN info "Downloading ${archive}..." if ! curl -sSfL "${download_url}" -o "${tmp_dir}/${archive}"; then warn "${binary} not available for ${version} — skipping" return 0 fi if curl -sSfL "${checksum_url}" -o "${tmp_dir}/checksums.txt" 2>/dev/null; then info "Verifying checksum..." (cd "${tmp_dir}" && verify_checksum "${archive}" "checksums.txt") else warn "No checksums.txt found for ${version} — skipping verification" fi info "Extracting ${archive}..." tar -xzf "${tmp_dir}/${archive}" -C "${tmp_dir}" local src="${tmp_dir}/${binary}" if [[ ! -f "${src}" ]]; then fatal "Expected binary '${binary}' not found in archive" fi mkdir -p "${INSTALL_DIR}" install -m 755 "${src}" "${INSTALL_DIR}/${binary}" success "${binary} ${version} installed to ${INSTALL_DIR}/${binary}" } # PATH setup # Adds INSTALL_DIR to PATH in the user's shell profile if not already present. setup_path() { local export_line="export PATH=\"${INSTALL_DIR}:\$PATH\"" # Detect which profile file to update local profile_file local shell_name shell_name=$(basename "${SHELL:-bash}") case "${shell_name}" in zsh) profile_file="${ZDOTDIR:-${HOME}}/.zshrc" ;; fish) profile_file="${HOME}/.config/fish/config.fish" export_line="set -gx PATH \"${INSTALL_DIR}\" \$PATH" ;; *) profile_file="${HOME}/.bashrc" ;; esac # Check if already in PATH or profile if echo "${PATH}" | tr ':' '\n' | grep -qxF "${INSTALL_DIR}"; then return 0 fi if [[ -f "${profile_file}" ]] && grep -qF "${INSTALL_DIR}" "${profile_file}"; then return 0 fi echo "" >> "${profile_file}" echo "# Orkestra CLI" >> "${profile_file}" echo "${export_line}" >> "${profile_file}" info "Added ${INSTALL_DIR} to PATH in ${profile_file}" warn "Restart your shell or run: export PATH=\"${INSTALL_DIR}:\$PATH\"" } # Shell completion install_completion() { if [[ "${SKIP_COMPLETION}" == "true" ]]; then info "Skipping shell completion (ORK_SKIP_COMPLETION=true)" return fi local shell_name shell_name=$(basename "${SHELL:-bash}") case "${shell_name}" in bash) local dir="${HOME}/.bash_completion.d" mkdir -p "${dir}" info "Installing bash completion → ${dir}/ork" "${INSTALL_DIR}/ork" completion bash > "${dir}/ork" 2>/dev/null || true echo " Source with: source ${dir}/ork (or restart your shell)" ;; zsh) local dir="${HOME}/.zsh/completions" mkdir -p "${dir}" info "Installing zsh completion → ${dir}/_ork" "${INSTALL_DIR}/ork" completion zsh > "${dir}/_ork" 2>/dev/null || true echo " Add to ~/.zshrc if not present: fpath=(${dir} \$fpath)" ;; fish) local dir="${HOME}/.config/fish/completions" mkdir -p "${dir}" info "Installing fish completion → ${dir}/ork.fish" "${INSTALL_DIR}/ork" completion fish > "${dir}/ork.fish" 2>/dev/null || true ;; *) warn "Shell '${shell_name}' not recognised — skipping completion." warn "Run 'ork completion ' to generate it manually." return ;; esac success "Shell completion installed for ${shell_name}" } # Post-install summary print_summary() { local version="$1" echo success "Installation complete (${version})" echo for binary in "${BINARIES[@]}"; do if component_skipped "${binary}"; then continue fi local bin_path="${INSTALL_DIR}/${binary}" if [[ -x "${bin_path}" ]]; then echo -e " ${GREEN}✓${RESET} ${bin_path}" fi done echo echo -e " ${BOLD}Get started:${RESET}" echo -e " ork init my-operator Scaffold a new operator" echo -e " ork validate Validate a Katalog" echo -e " ork run Start the operator runtime" echo -e " ork push name:v1 ./dir Push a pattern to the registry" echo echo -e " ${BOLD}Control Center:${RESET}" echo -e " ork control Start the web UI (port 8081)" echo echo -e " ${BOLD}Documentation:${RESET}" echo -e " https://github.com/${REPO}" echo } # Main main() { check_deps local platform version platform=$(detect_platform) version=$(resolve_version) info "Platform: ${platform} Version: ${version}" info "Install directory: ${INSTALL_DIR}" echo for binary in "${BINARIES[@]}"; do install_component "${binary}" "${platform}" "${version}" done setup_path install_completion print_summary "${version}" } main