#!/bin/sh # anode installer — https://get.coder.company/anode.sh # Usage: curl -fsSL get.coder.company/anode.sh | sh # # Environment variables: # ANODE_INSTALL_DIR Override install directory (default: ~/.local/bin or /usr/local/bin for root) # ANODE_VERSION Pin a specific version (default: latest) set -e # ─── Colors ────────────────────────────────────────────────────────────────── if [ -t 1 ] && [ -n "$(tput colors 2>/dev/null)" ]; then LIME=$(printf '\033[38;2;158;255;36m') PURPLE=$(printf '\033[38;2;154;92;255m') WHITE=$(printf '\033[97m') DIM=$(printf '\033[2m') RED=$(printf '\033[31m') YELLOW=$(printf '\033[33m') BOLD=$(printf '\033[1m') RESET=$(printf '\033[0m') else LIME="" PURPLE="" WHITE="" DIM="" RED="" YELLOW="" BOLD="" RESET="" fi # ─── Output helpers ────────────────────────────────────────────────────────── info() { printf " %s%s%s\n" "$DIM" "$1" "$RESET"; } step() { printf " %s▸%s %s%s%s\n" "$LIME" "$RESET" "$WHITE" "$1" "$RESET"; } ok() { printf " %s✓%s %s\n" "$LIME" "$RESET" "$1"; } fail() { printf " %s✗ %s%s\n" "$RED" "$1" "$RESET" >&2; } warn() { printf " %s! %s%s\n" "$YELLOW" "$1" "$RESET"; } die() { fail "$1"; exit 1; } # ─── Logo ──────────────────────────────────────────────────────────────────── show_logo() { printf "\n" printf "%s ▄▄▄▄▄▄▄▄▄▄▄▄▄▄%s\n" "$LIME" "$RESET" printf "%s ▀█████████████%s\n" "$LIME" "$RESET" printf "%s ▀█████████████▄%s\n" "$LIME" "$RESET" printf "%s ▀██████▄%s\n" "$LIME" "$RESET" printf "%s ██████▄%s\n" "$LIME" "$RESET" printf "%s ▄▄▄▄▄▄▄ ▀██████%s\n" "$LIME" "$RESET" printf "%s ▄██████▀ ▀██████▄%s\n" "$LIME" "$RESET" printf "%s ███████▀ ██████▄%s\n" "$LIME" "$RESET" printf "%s ▄███████ ██████▄%s\n" "$LIME" "$RESET" printf "%s███████████████▀ ▀██████%s\n" "$LIME" "$RESET" printf "%s██████████████▀ ▀██████%s\n" "$LIME" "$RESET" printf "%s███████████████▄ ▄██████▀%s\n" "$LIME" "$RESET" printf "%s▀▀▀▀▀▀▀▀▀▀██████▄ ▄██████%s\n" "$LIME" "$RESET" printf "%s ▀██████▄ ▄██████%s\n" "$LIME" "$RESET" printf "%s ███████ ██████▀%s\n" "$LIME" "$RESET" printf "%s ▀▀▀▀▀▀█ ▄██████▀%s\n" "$LIME" "$RESET" printf "%s ▄██████%s\n" "$LIME" "$RESET" printf "%s ███████%s\n" "$LIME" "$RESET" printf "%s ▄█████████████▀%s\n" "$LIME" "$RESET" printf "%s ▄█████████████▀%s\n" "$LIME" "$RESET" printf "%s ▀▀▀▀▀▀▀▀▀▀▀▀▀%s\n" "$LIME" "$RESET" printf "\n" printf " %s%sanode%s %s— terminal-native ai coding agent%s\n" "$BOLD" "$LIME" "$RESET" "$DIM" "$RESET" printf "\n" } # ─── Completion banner ─────────────────────────────────────────────────────── show_complete() { local version="$1" printf "\n" printf " %s━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━%s\n" "$LIME" "$RESET" printf "\n" printf " %s✓%s %s%sanode %s%s installed%s\n" "$LIME" "$RESET" "$BOLD" "$WHITE" "$version" "$RESET$DIM" "$RESET" printf "\n" printf " %sget started%s\n" "$DIM" "$RESET" printf " %sanode%s %slaunch the agent%s\n" "$WHITE" "$RESET" "$DIM" "$RESET" printf " %sanode -x \"prompt\"%s %sone-shot execute%s\n" "$WHITE" "$RESET" "$DIM" "$RESET" printf " %sanode --help%s %ssee all commands%s\n" "$WHITE" "$RESET" "$DIM" "$RESET" printf "\n" printf " %s━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━%s\n" "$LIME" "$RESET" printf "\n" } # ─── Prerequisite checks ──────────────────────────────────────────────────── need_cmd() { if ! command -v "$1" >/dev/null 2>&1; then die "Required command not found: $1" fi } check_downloader() { if command -v curl >/dev/null 2>&1; then DOWNLOADER="curl" elif command -v wget >/dev/null 2>&1; then DOWNLOADER="wget" else die "Neither curl nor wget found. Install one and retry." fi } download() { local url="$1" dest="$2" if [ "$DOWNLOADER" = "curl" ]; then curl -fsSL "$url" -o "$dest" else wget -qO "$dest" "$url" fi } fetch() { local url="$1" if [ "$DOWNLOADER" = "curl" ]; then curl -fsSL "$url" else wget -qO- "$url" fi } # ─── Platform detection ────────────────────────────────────────────────────── detect_os() { local uname_s uname_s=$(uname -s) case "$uname_s" in Linux*) OS="linux" ;; Darwin*) OS="darwin" ;; *) die "Unsupported OS: $uname_s (Linux and macOS only)" ;; esac } detect_arch() { local uname_m uname_m=$(uname -m) case "$uname_m" in x86_64|amd64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; *) die "Unsupported architecture: $uname_m (x64 and arm64 only)" ;; esac if [ "$OS" = "darwin" ] && [ "$ARCH" = "amd64" ]; then if sysctl -n sysctl.proc_translated 2>/dev/null | grep -q 1; then ARCH="arm64" info "rosetta detected — using native arm64 binary" fi fi } platform_string() { PLATFORM="${OS}-${ARCH}" } # ─── Install directory ─────────────────────────────────────────────────────── determine_install_dir() { if [ -n "${ANODE_INSTALL_DIR:-}" ]; then INSTALL_DIR="$ANODE_INSTALL_DIR" elif [ "$(id -u)" = "0" ]; then INSTALL_DIR="/usr/local/bin" else INSTALL_DIR="$HOME/.local/bin" fi } ensure_install_dir() { if [ ! -d "$INSTALL_DIR" ]; then mkdir -p "$INSTALL_DIR" fi } check_path() { case ":$PATH:" in *":$INSTALL_DIR:"*) return 0 ;; esac local shell_name shell_rc shell_name=$(basename "${SHELL:-/bin/sh}") case "$shell_name" in bash) shell_rc="$HOME/.bashrc" ;; zsh) shell_rc="$HOME/.zshrc" ;; fish) shell_rc="$HOME/.config/fish/config.fish" ;; *) shell_rc="$HOME/.profile" ;; esac printf "\n" warn "$INSTALL_DIR is not in your PATH" printf "\n" if [ "$shell_name" = "fish" ]; then printf " %sfish_add_path %s%s\n" "$WHITE" "$INSTALL_DIR" "$RESET" else printf " %sexport PATH=\"%s:\$PATH\"%s\n" "$WHITE" "$INSTALL_DIR" "$RESET" fi printf "\n" info "add to $shell_rc to make permanent" } # ─── Version resolution ────────────────────────────────────────────────────── resolve_version() { if [ -n "${ANODE_VERSION:-}" ]; then VERSION="$ANODE_VERSION" else VERSION=$(fetch "https://get.coder.company/releases/latest") || die "Failed to fetch latest version" fi } # ─── Checksum verification ─────────────────────────────────────────────────── verify_checksum() { local binary_path="$1" expected_sha256 local manifest manifest=$(fetch "https://get.coder.company/releases/${VERSION}/manifest.json") || { warn "checksum skipped (no manifest)" return 0 } expected_sha256=$(printf '%s' "$manifest" | grep -o "\"${PLATFORM}\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"[a-f0-9]*\"" | grep -o '[a-f0-9]\{64\}') if [ -z "$expected_sha256" ]; then warn "checksum skipped (platform not in manifest)" return 0 fi local actual_sha256 if command -v sha256sum >/dev/null 2>&1; then actual_sha256=$(sha256sum "$binary_path" | cut -d' ' -f1) elif command -v shasum >/dev/null 2>&1; then actual_sha256=$(shasum -a 256 "$binary_path" | cut -d' ' -f1) else warn "checksum skipped (no sha256sum)" return 0 fi if [ "$actual_sha256" != "$expected_sha256" ]; then fail "Checksum mismatch — binary may be corrupted" fail " expected: $expected_sha256" fail " got: $actual_sha256" die "Aborting install." fi } # ─── Temp file cleanup ─────────────────────────────────────────────────────── TMPFILE="" cleanup() { if [ -n "$TMPFILE" ] && [ -f "$TMPFILE" ]; then rm -f "$TMPFILE" fi } trap cleanup EXIT INT TERM # ─── Main ──────────────────────────────────────────────────────────────────── main() { show_logo check_downloader need_cmd uname need_cmd chmod detect_os detect_arch platform_string determine_install_dir ensure_install_dir resolve_version step "platform ${OS}/${ARCH}" step "version ${VERSION}" step "target ${INSTALL_DIR}/anode" # Check for existing install if [ -f "$INSTALL_DIR/anode" ]; then local existing existing=$("$INSTALL_DIR/anode" --version 2>/dev/null | head -1 || echo "unknown") info "upgrading from $existing" fi printf "\n" # Download local url="https://get.coder.company/releases/${VERSION}/${PLATFORM}/anode" step "downloading" TMPFILE=$(mktemp) if ! download "$url" "$TMPFILE"; then die "Download failed — check that $VERSION exists for $PLATFORM" fi ok "downloaded" # Verify step "verifying checksum" verify_checksum "$TMPFILE" ok "verified" # Install step "installing" chmod +x "$TMPFILE" mv "$TMPFILE" "$INSTALL_DIR/anode" TMPFILE="" ok "installed to $INSTALL_DIR/anode" check_path show_complete "$VERSION" } main "$@"