#!/bin/sh # Published copies are stamped by build-cli-release.mjs. Compatible with `sh`. # Installer revision: 2026-09-09.1 (CLI package version is independent). set -eu main() { release='0.0.4-microsoft-preview.34' base='https://tools.lambogenius.com/lambogenius-integration-migration' dependencies=1 prefix="${LI_INSTALL_PREFIX:-$HOME/.local}" while [ "$#" -gt 0 ]; do case "$1" in --skip-dependencies) dependencies=0 ;; --version) shift; release=${1:?Missing version} ;; --prefix) shift; prefix=${1:?Missing prefix} ;; --help) printf '%s\n' 'Usage: sh install-cli.sh [--version VERSION] [--skip-dependencies] [--prefix PATH]' 'Installs missing runtime tools by default. AI provider selection/authentication is separate.'; return ;; *) printf 'Unknown argument: %s\n' "$1" >&2; return 1 ;; esac shift done case "$release" in ''|*[!a-zA-Z0-9.-]*|__*) echo 'Invalid or unstamped release version' >&2; return 1;; esac case "$prefix" in /*) ;; *) echo 'Prefix must be an absolute path' >&2; return 1;; esac case "$base" in https://*) ;; *) echo 'Release URL must use HTTPS' >&2; return 1;; esac os=$(uname -s) case "$os" in Linux|Darwin) ;; *) echo 'Use install-cli.ps1 on Windows.' >&2; return 1;; esac tmp=$(mktemp -d "${TMPDIR:-/tmp}/li-install.XXXXXXXX") # Only the directory created by mktemp is removed; never the install prefix. trap 'rm -rf -- "$tmp"' EXIT HUP INT TERM download() { if command -v curl >/dev/null 2>&1; then curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' "$1" -o "$2" elif command -v wget >/dev/null 2>&1; then wget --https-only -qO "$2" "$1" else echo 'curl or wget is required to download packages.' >&2; return 1; fi } elevated() { if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi; } has() { command -v "$1" >/dev/null 2>&1; } node_ok() { has node && node -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 20 ? 0 : 1)'; } npm_ok() { has npm && npm --version >/dev/null 2>&1; } git_ok() { has git && git --version >/dev/null 2>&1; } pwsh_ok() { has pwsh && pwsh -NoProfile -NonInteractive -Command 'if ($PSVersionTable.PSVersion.Major -lt 7) { exit 1 }' >/dev/null 2>&1; } az_ok() { has az && az version >/dev/null 2>&1; } msi_ok() { has msiextract || has 7zz || has 7z; } cab_ok() { has cabextract || has 7zz || has 7z; } missing_dependencies() { missing='' node_ok && npm_ok || missing="$missing Node.js>=20/npm" git_ok || missing="$missing Git" pwsh_ok || missing="$missing PowerShell7" az_ok || missing="$missing AzureCLI" msi_ok || missing="$missing MSI-extractor" cab_ok || missing="$missing CAB-extractor" has tar || missing="$missing tar" has gzip || missing="$missing gzip" has curl || has wget || missing="$missing curl/wget" } export PATH="$prefix/bin:$PATH" if [ "$dependencies" -eq 1 ]; then echo 'Checking migration prerequisites...' missing_dependencies if [ -n "$missing" ]; then printf 'Missing or unusable prerequisites:%s\n' "$missing" echo 'System packages may require administrator approval. Use --skip-dependencies to manage prerequisites yourself.' fi if [ -z "$missing" ]; then echo 'Required tools are already available; no system packages need installing.' elif [ "$os" = Darwin ]; then if ! has brew; then echo 'Homebrew is required. Install it from https://brew.sh, then rerun (or use --skip-dependencies).' >&2 return 1 fi git_ok || brew install git pwsh_ok || brew install powershell az_ok || brew install azure-cli msi_ok || brew install msitools cab_ok || brew install cabextract has tar || brew install gnu-tar has gzip || brew install gzip has curl || has wget || brew install curl if ! node_ok || ! npm_ok; then brew install node@22; export PATH="$(brew --prefix node@22)/bin:$PATH"; fi else # Explicit support avoids installing repositories for the wrong distribution. ID='' VERSION_ID='' PRETTY_NAME='unknown Linux' if [ -r /etc/os-release ]; then . /etc/os-release; fi case "$ID:$VERSION_ID" in ubuntu:22.04|ubuntu:24.04|ubuntu:26.04|debian:12|debian:13) ;; *) printf 'Automatic dependency setup is unavailable for %s (%s:%s). Missing:%s\n' "$PRETTY_NAME" "$ID" "$VERSION_ID" "$missing" >&2 echo 'Supported Linux versions: Ubuntu 22.04/24.04/26.04 and Debian 12/13. Install prerequisites manually, then rerun; --skip-dependencies installs only the CLI.' >&2; return 1;; esac has apt-get && has dpkg || { echo 'This distribution requires apt-get and dpkg for automatic setup.' >&2; return 1; } if ! node_ok || ! npm_ok || ! pwsh_ok || ! az_ok; then case "$(uname -m)" in x86_64|aarch64|arm64) ;; *) echo 'Automatic Node.js/Microsoft tool installation requires x86_64 or ARM64. Install compatible prerequisites manually.' >&2; return 1;; esac fi packages='' [ "$(dpkg-query -W -f='${Status}' ca-certificates 2>/dev/null || :)" = 'install ok installed' ] || packages="$packages ca-certificates" has curl || has wget || packages="$packages curl" git_ok || packages="$packages git" has tar || packages="$packages tar" has gzip || packages="$packages gzip" msi_ok || packages="$packages msitools" cab_ok || packages="$packages cabextract" if [ -n "$packages" ]; then elevated apt-get update # Package names above are fixed constants, not user input. elevated apt-get install -y $packages fi if ! pwsh_ok; then if [ "$ID:$VERSION_ID" = ubuntu:26.04 ]; then # The 26.04 repository has no PowerShell package yet. Use Microsoft's # universal package, pinned to the official release's SHA-256 digest. case "$(uname -m)" in x86_64) pwsh_arch=amd64; pwsh_sha=9585f38ab5a026c3fc0995486e26e12050777960fef47a22dca98b577c5d27a7;; aarch64|arm64) pwsh_arch=arm64; pwsh_sha=56aa313d5474602233e97a57104b661daf4a8c35fc91f1eb664938bcce8e11da;; esac download "https://github.com/PowerShell/PowerShell/releases/download/v7.6.6/powershell_7.6.6-1.deb_$pwsh_arch.deb" "$tmp/powershell.deb" printf '%s %s\n' "$pwsh_sha" "$tmp/powershell.deb" | sha256sum -c - elevated apt-get update elevated apt-get install -y "$tmp/powershell.deb" else download "https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb" "$tmp/microsoft.deb" elevated dpkg -i "$tmp/microsoft.deb" elevated apt-get update elevated apt-get install -y powershell fi fi if ! az_ok; then # Microsoft's installer uses curl even when this script was downloaded with wget. if ! has curl; then elevated apt-get update; elevated apt-get install -y curl; fi download https://aka.ms/InstallAzureCLIDeb "$tmp/azure-cli.sh" elevated bash "$tmp/azure-cli.sh" fi if ! node_ok || ! npm_ok; then case "$(uname -m)" in x86_64) arch=x64;; aarch64|arm64) arch=arm64;; *) echo 'Unsupported Node.js architecture' >&2; return 1;; esac download https://nodejs.org/download/release/latest-v22.x/SHASUMS256.txt "$tmp/node-checksums" node_archive=$(awk -v suffix="-linux-$arch.tar.gz" '$2 ~ suffix "$" {print $2}' "$tmp/node-checksums") case "$node_archive" in node-v22.*-linux-*.tar.gz) ;; *) echo 'Cannot resolve Node.js archive' >&2; return 1;; esac download "https://nodejs.org/download/release/latest-v22.x/$node_archive" "$tmp/$node_archive" (cd "$tmp"; awk -v name="$node_archive" '$2 == name' node-checksums | sha256sum -c -) mkdir -p "$prefix" tar -xzf "$tmp/$node_archive" -C "$prefix" --strip-components=1 fi fi missing_dependencies [ -z "$missing" ] || { printf 'Prerequisites are still missing or unusable:%s. Fix these tools and rerun.\n' "$missing" >&2; return 1; } if ! az bicep version >/dev/null 2>&1; then echo 'Installing missing Bicep CLI...' az bicep install az bicep version >/dev/null 2>&1 || { echo 'Bicep CLI could not be verified. Run az bicep install, then rerun.' >&2; return 1; } fi fi node_ok || { echo 'Node.js >=20 is required.' >&2; return 1; } npm_ok || { echo 'A working npm is required.' >&2; return 1; } download "$base/$release/release.json" "$tmp/release.json" # Parse data as JSON, never as shell code. The filename is derived from a validated version. node -e 'const fs=require("fs");const m=JSON.parse(fs.readFileSync(process.argv[1]));if(m.version!==process.argv[2]||!/^[a-f0-9]{64}$/.test(m.sha256))throw Error("Invalid release manifest")' "$tmp/release.json" "$release" archive="lambogenius-integration-migration-cli-$release.tgz" download "$base/$release/$archive" "$tmp/cli.tgz" node -e 'const fs=require("fs"),c=require("crypto");const m=JSON.parse(fs.readFileSync(process.argv[1]));if(c.createHash("sha256").update(fs.readFileSync(process.argv[2])).digest("hex")!==m.sha256)throw Error("CLI checksum mismatch")' "$tmp/release.json" "$tmp/cli.tgz" npm install --global --ignore-scripts --no-audit --no-fund --prefix "$prefix" "$tmp/cli.tgz" "$prefix/bin/li" --version printf '\nInstalled. Add this directory to your shell PATH: %s/bin\n' "$prefix" if [ "$os" = Darwin ] && has brew && [ -d "$(brew --prefix)/opt/node@22/bin" ]; then printf 'If using Homebrew Node 22, also add to PATH: %s/opt/node@22/bin\n' "$(brew --prefix)" fi printf '%s\n' 'Next: select and authenticate an AI provider (li auth --help).' 'Then generate a code at https://lambogenius.com/app/licensing and run: li license activate --code ' if [ "$dependencies" -eq 0 ]; then missing_dependencies [ -z "$missing" ] || printf 'CLI installed; migration prerequisites still need attention:%s\n' "$missing" fi } main "$@"