#!/bin/sh # Published copies are stamped by build-cli-release.mjs. Compatible with `sh`. set -eu main() { release='0.0.1' 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)'; } export PATH="$prefix/bin:$PATH" if [ "$dependencies" -eq 1 ]; then printf '%s\n' 'Installing missing dependencies: Node.js/npm, Git, PowerShell, Azure CLI/Bicep, MSI/CAB tools.' 'System packages may require administrator approval. Use --skip-dependencies to opt out.' if [ "$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 has git || brew install git has pwsh || brew install powershell has az || brew install azure-cli has msiextract || brew install msitools has cabextract || brew install cabextract if ! node_ok || ! has npm; then brew install node@22; export PATH="$(brew --prefix node@22)/bin:$PATH"; fi else # Explicit support avoids installing repositories for the wrong distribution. . /etc/os-release case "$ID:$VERSION_ID" in ubuntu:22.04|ubuntu:24.04|debian:12|debian:13) ;; *) echo 'Automatic Linux dependencies support Ubuntu 22.04/24.04 and Debian 12/13. On other distributions install prerequisites, then use --skip-dependencies.' >&2; return 1;; esac elevated apt-get update elevated apt-get install -y ca-certificates curl git tar gzip msitools cabextract if ! has pwsh; then 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 if ! has az; then download https://aka.ms/InstallAzureCLIDeb "$tmp/azure-cli.sh" elevated bash "$tmp/azure-cli.sh" fi if ! node_ok || ! has npm; 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 az bicep version >/dev/null 2>&1 || az bicep install fi node_ok || { echo 'Node.js >=20 is required.' >&2; return 1; } has npm || { echo '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 ' for dependency in git pwsh az; do has "$dependency" || printf 'Still required for migration: %s\n' "$dependency"; done } main "$@"