Add cross-platform bootstrap script for host environment setup
scripts/bootstrap.sh verifies and (optionally) installs everything mcesptool needs on a host: system packages, ESP-IDF toolchains including the optional QEMU + esp-clang that install.sh all skips, uv, and the project venv. Supports Arch Linux (pacman) and macOS (Homebrew, with cautious defaults that prefer user-owned ~/homebrew). Runs idempotent: safe to re-run after a system reinstall or when extending to a new dev machine. make bootstrap-check for read-only verification; make bootstrap for install.
This commit is contained in:
parent
28b12909ca
commit
349351deec
15
Makefile
15
Makefile
@ -1,14 +1,16 @@
|
||||
# MCP ESPTool Server Makefile
|
||||
|
||||
.PHONY: help install dev test lint format clean docker-build docker-up docker-down docker-logs
|
||||
.PHONY: help bootstrap bootstrap-check install dev test lint format clean docker-build docker-up docker-down docker-logs
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@echo "MCP ESPTool Server Development Commands"
|
||||
@echo ""
|
||||
@echo "Setup & Installation:"
|
||||
@echo " install Install project with uv"
|
||||
@echo " dev Install in development mode"
|
||||
@echo " bootstrap-check Verify host has everything mcesptool needs (read-only)"
|
||||
@echo " bootstrap Install missing system pkgs, IDF tools, venv, group access"
|
||||
@echo " install Install project with uv"
|
||||
@echo " dev Install in development mode"
|
||||
@echo ""
|
||||
@echo "Development:"
|
||||
@echo " test Run test suite"
|
||||
@ -26,6 +28,13 @@ help:
|
||||
@echo " mcp-install Install server with Claude Code"
|
||||
@echo " mcp-test Test MCP server integration"
|
||||
|
||||
# Bootstrap (host-level environment setup: pacman/brew, ESP-IDF, uv, venv)
|
||||
bootstrap-check:
|
||||
@bash scripts/bootstrap.sh --check
|
||||
|
||||
bootstrap:
|
||||
@bash scripts/bootstrap.sh --install
|
||||
|
||||
# Installation
|
||||
install:
|
||||
uv sync
|
||||
|
||||
412
scripts/bootstrap.sh
Executable file
412
scripts/bootstrap.sh
Executable file
@ -0,0 +1,412 @@
|
||||
#!/usr/bin/env bash
|
||||
# bootstrap.sh — cross-platform environment check & setup for mcp-esptool
|
||||
#
|
||||
# Verifies system packages, ESP-IDF state, project venv, QEMU support, and
|
||||
# (on Linux) serial-port group membership. Idempotent: safe to re-run.
|
||||
#
|
||||
# Supports:
|
||||
# - Linux (Arch: pacman)
|
||||
# - macOS 13+ (Homebrew)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/bootstrap.sh # check-only (default)
|
||||
# ./scripts/bootstrap.sh --check # check-only (explicit)
|
||||
# ./scripts/bootstrap.sh --install # check + install missing pieces
|
||||
# ./scripts/bootstrap.sh --help
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Make this script self-sufficient regardless of the invoking shell's config:
|
||||
# prepend common user-tool locations to PATH so uv (installed to ~/.local/bin) is
|
||||
# always findable during bootstrap runs. Non-invasive — only affects THIS process,
|
||||
# not the user's login environment.
|
||||
for user_bin in "$HOME/.local/bin" "$HOME/homebrew/bin"; do
|
||||
case ":$PATH:" in
|
||||
*":$user_bin:"*) : ;;
|
||||
*) [[ -d "$user_bin" ]] && PATH="$user_bin:$PATH" ;;
|
||||
esac
|
||||
done
|
||||
export PATH
|
||||
|
||||
# ─── colors ────────────────────────────────────────────────────────────────
|
||||
if [[ -t 1 ]]; then
|
||||
C_OK=$'\e[32m'; C_WARN=$'\e[33m'; C_ERR=$'\e[31m'
|
||||
C_DIM=$'\e[2m'; C_BOLD=$'\e[1m'; C_RESET=$'\e[0m'
|
||||
else
|
||||
C_OK=''; C_WARN=''; C_ERR=''; C_DIM=''; C_BOLD=''; C_RESET=''
|
||||
fi
|
||||
|
||||
ok() { printf " ${C_OK}✓${C_RESET} %s\n" "$*"; }
|
||||
warn() { printf " ${C_WARN}⚠${C_RESET} %s\n" "$*"; }
|
||||
miss() { printf " ${C_ERR}✗${C_RESET} %s\n" "$*"; }
|
||||
hdr() { printf "\n${C_BOLD}%s${C_RESET}\n" "$*"; }
|
||||
dim() { printf " ${C_DIM}%s${C_RESET}\n" "$*"; }
|
||||
|
||||
# ─── OS detection + per-OS package registry ─────────────────────────────────
|
||||
OS_KIND="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH="$(uname -m)"
|
||||
MODE="check"
|
||||
MISSING=0
|
||||
|
||||
case "$OS_KIND" in
|
||||
linux)
|
||||
if [[ ! -f /etc/arch-release ]]; then
|
||||
miss "This bootstrap only supports Arch Linux and macOS. Detected: $(uname -sr)"
|
||||
exit 2
|
||||
fi
|
||||
PKG_MGR="pacman"
|
||||
REQUIRED_SYS_PKGS=(ninja ccache dfu-util)
|
||||
OPTIONAL_SYS_PKGS=(libxml2-legacy) # libxml2.so.2 shim for IDF-bundled esp-clang
|
||||
NEEDS_SERIAL_GROUP=1
|
||||
SERIAL_GROUP="uucp" # Arch convention
|
||||
;;
|
||||
darwin)
|
||||
PKG_MGR="brew"
|
||||
REQUIRED_SYS_PKGS=(ninja ccache dfu-util cmake libusb)
|
||||
OPTIONAL_SYS_PKGS=() # No libxml2 SONAME issue on macOS
|
||||
NEEDS_SERIAL_GROUP=0 # macOS grants serial to console user
|
||||
SERIAL_GROUP=""
|
||||
;;
|
||||
*)
|
||||
miss "Unsupported OS: $OS_KIND"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# ─── arg parsing ───────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--check) MODE="check"; shift ;;
|
||||
--install) MODE="install"; shift ;;
|
||||
--help|-h)
|
||||
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0 ;;
|
||||
*) miss "Unknown arg: $1"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ─── package-manager abstraction ──────────────────────────────────────────
|
||||
pkg_installed() {
|
||||
case "$PKG_MGR" in
|
||||
pacman) pacman -Qq "$1" >/dev/null 2>&1 ;;
|
||||
brew) brew list --formula --versions "$1" >/dev/null 2>&1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pkg_install() {
|
||||
case "$PKG_MGR" in
|
||||
pacman) need_sudo; sudo pacman -S --noconfirm --needed "$1" ;;
|
||||
brew) brew install "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pkg_install_hint() {
|
||||
case "$PKG_MGR" in
|
||||
pacman) printf "sudo pacman -S --needed %s" "$1" ;;
|
||||
brew) printf "brew install %s" "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── config ────────────────────────────────────────────────────────────────
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
IDF_DIR="${IDF_PATH:-$HOME/esp/esp-idf}"
|
||||
IDF_TOOLS_DIR="${IDF_TOOLS_PATH:-$HOME/.espressif}"
|
||||
|
||||
# ─── helpers ───────────────────────────────────────────────────────────────
|
||||
need_sudo() {
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
printf " ${C_DIM}(sudo needed — you may be prompted)${C_RESET}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
run_or_print() {
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
printf " ${C_DIM}\$ %s${C_RESET}\n" "$*"
|
||||
eval "$@"
|
||||
else
|
||||
printf " ${C_DIM}fix:${C_RESET} %s\n" "$*"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── macOS prerequisites (Xcode CLT + Homebrew + uv) ───────────────────────
|
||||
if [[ "$OS_KIND" == "darwin" ]]; then
|
||||
hdr "▸ macOS prerequisites"
|
||||
# Xcode Command Line Tools
|
||||
if xcode-select -p >/dev/null 2>&1; then
|
||||
ok "Xcode Command Line Tools ($(xcode-select -p))"
|
||||
else
|
||||
miss "Xcode Command Line Tools missing"
|
||||
MISSING=$((MISSING + 1))
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
warn "Launching 'xcode-select --install' — accept the GUI prompt, then re-run this script."
|
||||
xcode-select --install || true
|
||||
else
|
||||
dim "fix: xcode-select --install (opens a GUI prompt)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Homebrew — prefer user-owned ~/homebrew by default (cautious: never mutates shared
|
||||
# system state). Set MCESPTOOL_ALLOW_SHARED_BREW=1 to opt into using an existing
|
||||
# writable /opt/homebrew or /usr/local when ~/homebrew doesn't exist.
|
||||
BREW_FOUND=""
|
||||
if [[ -x "$HOME/homebrew/bin/brew" ]]; then
|
||||
# User-side install already exists — always use it.
|
||||
eval "$("$HOME/homebrew/bin/brew" shellenv)"
|
||||
BREW_FOUND="$HOME/homebrew"
|
||||
elif [[ "${MCESPTOOL_ALLOW_SHARED_BREW:-0}" == "1" ]]; then
|
||||
# Opt-in path: check shared prefixes for a writable install.
|
||||
for BREW_CANDIDATE in /opt/homebrew/bin/brew /usr/local/bin/brew; do
|
||||
if [[ -x "$BREW_CANDIDATE" ]]; then
|
||||
BREW_PREFIX="$("$BREW_CANDIDATE" --prefix 2>/dev/null)"
|
||||
if [[ -n "$BREW_PREFIX" ]] && [[ -w "$BREW_PREFIX/Cellar" || -w "$BREW_PREFIX" ]]; then
|
||||
eval "$("$BREW_CANDIDATE" shellenv)"
|
||||
BREW_FOUND="$BREW_PREFIX"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "$BREW_FOUND" ]]; then
|
||||
ok "Homebrew ($(brew --version | head -1)) at $BREW_FOUND"
|
||||
# Informational: mention any shared brew we're NOT using
|
||||
for OTHER in /opt/homebrew /usr/local; do
|
||||
if [[ "$OTHER" != "$BREW_FOUND" ]] && [[ -x "$OTHER/bin/brew" ]]; then
|
||||
dim "note: also found $OTHER/bin/brew (not used — set MCESPTOOL_ALLOW_SHARED_BREW=1 to prefer it)"
|
||||
fi
|
||||
done
|
||||
else
|
||||
miss "Homebrew not installed in user prefix (~/homebrew)"
|
||||
MISSING=$((MISSING + 1))
|
||||
# If a shared brew exists but we're skipping it, tell the user
|
||||
for OTHER in /opt/homebrew /usr/local; do
|
||||
if [[ -x "$OTHER/bin/brew" ]]; then
|
||||
dim "note: $OTHER/bin/brew exists (skipped — set MCESPTOOL_ALLOW_SHARED_BREW=1 to use it)"
|
||||
fi
|
||||
done
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
warn "Installing Homebrew to \$HOME/homebrew (user-owned, no sudo)"
|
||||
git clone --depth=1 https://github.com/Homebrew/brew "$HOME/homebrew"
|
||||
eval "$("$HOME/homebrew/bin/brew" shellenv)"
|
||||
else
|
||||
dim "fix: git clone --depth=1 https://github.com/Homebrew/brew ~/homebrew"
|
||||
dim " (user-owned prefix — no sudo; then add ~/homebrew/bin to PATH)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# uv (via official installer OR brew — official installer is more current)
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
ok "uv ($(uv --version 2>&1 | head -1))"
|
||||
else
|
||||
miss "uv missing"
|
||||
MISSING=$((MISSING + 1))
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
else
|
||||
dim "fix: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Note: uv's installer writes its PATH export to ~/.zshrc (interactive-only).
|
||||
# That means `ssh host 'uv ...'` won't find uv unless the user adds the same
|
||||
# line to ~/.zshenv (non-interactive). Bootstrap does NOT modify user shell
|
||||
# config — we make ourselves self-sufficient at the top of the script instead.
|
||||
# If the user wants uv on PATH for direct non-interactive SSH, hint at the fix.
|
||||
if [[ -f "$HOME/.local/bin/env" ]] && ! grep -q "local/bin/env" "$HOME/.zshenv" 2>/dev/null; then
|
||||
dim "note: 'ssh $(hostname) uv ...' won't find uv from a fresh shell — ~/.zshrc"
|
||||
dim " is interactive-only. To make uv available in non-interactive SSH, run:"
|
||||
dim " echo '. \"\$HOME/.local/bin/env\"' >> ~/.zshenv"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── system packages ───────────────────────────────────────────────────────
|
||||
hdr "▸ System packages ($PKG_MGR, required)"
|
||||
for pkg in "${REQUIRED_SYS_PKGS[@]}"; do
|
||||
if pkg_installed "$pkg"; then
|
||||
ok "$pkg"
|
||||
else
|
||||
miss "$pkg (missing)"
|
||||
MISSING=$((MISSING + 1))
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
pkg_install "$pkg"
|
||||
else
|
||||
dim "fix: $(pkg_install_hint "$pkg")"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#OPTIONAL_SYS_PKGS[@]} -gt 0 ]]; then
|
||||
hdr "▸ System packages ($PKG_MGR, optional)"
|
||||
for pkg in "${OPTIONAL_SYS_PKGS[@]}"; do
|
||||
if pkg_installed "$pkg"; then
|
||||
ok "$pkg"
|
||||
else
|
||||
warn "$pkg (optional — needed only for esp-clang)"
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
pkg_install "$pkg"
|
||||
else
|
||||
dim "fix: $(pkg_install_hint "$pkg")"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# ─── required CLI tools ───────────────────────────────────────────────────
|
||||
hdr "▸ Required CLI tools"
|
||||
tool_version() {
|
||||
case "$1" in
|
||||
esptool) esptool version 2>&1 | head -1 ;;
|
||||
*) "$1" --version 2>&1 | head -1 ;;
|
||||
esac
|
||||
}
|
||||
REQUIRED_TOOLS=(esptool cmake python3 uv uvx)
|
||||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||||
if command -v "$tool" >/dev/null 2>&1; then
|
||||
ok "$tool ($(tool_version "$tool"))"
|
||||
else
|
||||
miss "$tool not in PATH"
|
||||
MISSING=$((MISSING + 1))
|
||||
# esptool has a package on both platforms
|
||||
if [[ "$tool" == "esptool" ]] && [[ "$MODE" == "install" ]]; then
|
||||
pkg_install esptool || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── QEMU (system-provided; optional since IDF ships its own too) ─────────
|
||||
hdr "▸ QEMU (system, optional — IDF also ships its own)"
|
||||
for qbin in qemu-system-xtensa qemu-system-riscv32; do
|
||||
if command -v "$qbin" >/dev/null 2>&1; then
|
||||
ok "$qbin ($("$qbin" --version | head -1 | awk '{print $4}'))"
|
||||
else
|
||||
warn "$qbin not in PATH (IDF-bundled qemu-xtensa/qemu-riscv32 will still work)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─── ESP-IDF source ───────────────────────────────────────────────────────
|
||||
hdr "▸ ESP-IDF source"
|
||||
if [[ -d "$IDF_DIR/.git" ]]; then
|
||||
IDF_VERSION="$(cd "$IDF_DIR" && git describe --tags 2>/dev/null || echo 'unknown')"
|
||||
ok "Cloned at $IDF_DIR ($IDF_VERSION)"
|
||||
else
|
||||
miss "ESP-IDF not found at $IDF_DIR"
|
||||
MISSING=$((MISSING + 1))
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
mkdir -p "$(dirname "$IDF_DIR")"
|
||||
git clone --recursive --depth 1 --branch v5.3 https://github.com/espressif/esp-idf.git "$IDF_DIR"
|
||||
else
|
||||
dim "fix: git clone --recursive --depth 1 --branch v5.3 https://github.com/espressif/esp-idf.git $IDF_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── ESP-IDF tools ────────────────────────────────────────────────────────
|
||||
hdr "▸ ESP-IDF tools — required (installed by install.sh all)"
|
||||
REQUIRED_IDF_TOOLS=(
|
||||
xtensa-esp-elf xtensa-esp-elf-gdb
|
||||
riscv32-esp-elf riscv32-esp-elf-gdb
|
||||
esp32ulp-elf openocd-esp32 esp-rom-elfs
|
||||
)
|
||||
NEED_IDF_INSTALL=0
|
||||
for tool in "${REQUIRED_IDF_TOOLS[@]}"; do
|
||||
if [[ -d "$IDF_TOOLS_DIR/tools/$tool" ]] && [[ -n "$(ls -A "$IDF_TOOLS_DIR/tools/$tool" 2>/dev/null)" ]]; then
|
||||
ok "$tool"
|
||||
else
|
||||
miss "$tool"
|
||||
MISSING=$((MISSING + 1)); NEED_IDF_INSTALL=1
|
||||
fi
|
||||
done
|
||||
if [[ $NEED_IDF_INSTALL -eq 1 ]]; then
|
||||
if [[ "$MODE" == "install" ]] && [[ -d "$IDF_DIR" ]]; then
|
||||
printf " ${C_DIM}\$ %s${C_RESET}\n" "$IDF_DIR/install.sh all"
|
||||
IDF_TOOLS_PATH="$IDF_TOOLS_DIR" bash "$IDF_DIR/install.sh" all
|
||||
else
|
||||
dim "fix: $IDF_DIR/install.sh all"
|
||||
fi
|
||||
fi
|
||||
|
||||
hdr "▸ ESP-IDF tools — optional (QEMU + clang static analysis)"
|
||||
OPTIONAL_IDF_TOOLS=(qemu-xtensa qemu-riscv32 esp-clang)
|
||||
NEED_OPTIONAL=()
|
||||
for tool in "${OPTIONAL_IDF_TOOLS[@]}"; do
|
||||
if find "$IDF_TOOLS_DIR/tools/$tool" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | read -r; then
|
||||
ok "$tool"
|
||||
else
|
||||
warn "$tool (optional)"
|
||||
NEED_OPTIONAL+=("$tool")
|
||||
fi
|
||||
done
|
||||
if [[ ${#NEED_OPTIONAL[@]} -gt 0 ]]; then
|
||||
if [[ "$MODE" == "install" ]] && [[ -d "$IDF_DIR" ]]; then
|
||||
printf " ${C_DIM}\$ idf_tools.py install %s${C_RESET}\n" "${NEED_OPTIONAL[*]}"
|
||||
IDF_TOOLS_PATH="$IDF_TOOLS_DIR" python3 "$IDF_DIR/tools/idf_tools.py" install "${NEED_OPTIONAL[@]}"
|
||||
else
|
||||
dim "fix: IDF_TOOLS_PATH=$IDF_TOOLS_DIR python3 $IDF_DIR/tools/idf_tools.py install ${NEED_OPTIONAL[*]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── project venv ─────────────────────────────────────────────────────────
|
||||
hdr "▸ Project venv"
|
||||
# Pin Python 3.13 for the venv: pydantic-core 2.33.x has no pre-built 3.14 wheels
|
||||
# yet, so on stock-3.14 hosts (e.g. macOS Tahoe) `uv sync` falls through to a
|
||||
# Rust-toolchain source build and fails. Explicit 3.13 sidesteps that until
|
||||
# wheels catch up.
|
||||
VENV_PYTHON="3.13"
|
||||
if [[ -x "$PROJECT_DIR/.venv/bin/python" ]]; then
|
||||
PYV="$("$PROJECT_DIR/.venv/bin/python" --version 2>&1)"
|
||||
ok "$PROJECT_DIR/.venv ($PYV)"
|
||||
else
|
||||
miss "venv missing or broken"
|
||||
MISSING=$((MISSING + 1))
|
||||
run_or_print "cd $PROJECT_DIR && rm -rf .venv && uv sync --python $VENV_PYTHON"
|
||||
fi
|
||||
|
||||
# ─── serial access ────────────────────────────────────────────────────────
|
||||
hdr "▸ Serial port access"
|
||||
if [[ $NEEDS_SERIAL_GROUP -eq 1 ]]; then
|
||||
if id -nG "$USER" | tr ' ' '\n' | grep -qx "$SERIAL_GROUP"; then
|
||||
ok "User '$USER' is in '$SERIAL_GROUP' group"
|
||||
else
|
||||
miss "User '$USER' NOT in '$SERIAL_GROUP' group (flashing real chips will fail)"
|
||||
MISSING=$((MISSING + 1))
|
||||
if [[ "$MODE" == "install" ]]; then
|
||||
need_sudo
|
||||
sudo usermod -aG "$SERIAL_GROUP" "$USER"
|
||||
warn "Re-login required for group membership to take effect"
|
||||
else
|
||||
dim "fix: sudo usermod -aG $SERIAL_GROUP $USER (then re-login)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# macOS — no group gate. Just show what's plugged in.
|
||||
SERIAL_DEVS=$(ls /dev/cu.usbserial-* /dev/cu.usbmodem* /dev/cu.SLAB_USBtoUART 2>/dev/null || true)
|
||||
if [[ -n "$SERIAL_DEVS" ]]; then
|
||||
ok "Detected serial devices: $(echo "$SERIAL_DEVS" | tr '\n' ' ')"
|
||||
else
|
||||
ok "macOS grants serial access to the console user (no groups to configure)"
|
||||
dim "no serial adapters attached right now — will appear as /dev/cu.usbserial-* when connected"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── IDF env ──────────────────────────────────────────────────────────────
|
||||
hdr "▸ ESP-IDF environment"
|
||||
if [[ -n "${IDF_PATH:-}" ]]; then
|
||||
ok "IDF_PATH exported: $IDF_PATH"
|
||||
else
|
||||
warn "IDF_PATH not exported in this shell"
|
||||
dim "fix: source $IDF_DIR/export.sh (or alias 'get_idf')"
|
||||
fi
|
||||
|
||||
# ─── summary ───────────────────────────────────────────────────────────────
|
||||
hdr "═══ Summary ═══"
|
||||
printf " ${C_DIM}Platform: $OS_KIND/$ARCH · Package manager: $PKG_MGR${C_RESET}\n"
|
||||
if [[ $MISSING -eq 0 ]]; then
|
||||
printf " ${C_OK}${C_BOLD}All checks passed.${C_RESET}\n\n"
|
||||
exit 0
|
||||
elif [[ "$MODE" == "install" ]]; then
|
||||
printf " ${C_WARN}${C_BOLD}Install attempted. Re-run --check to verify.${C_RESET}\n\n"
|
||||
exit 0
|
||||
else
|
||||
printf " ${C_WARN}${C_BOLD}%d issue(s) found.${C_RESET} Re-run with ${C_BOLD}--install${C_RESET} to fix.\n\n" "$MISSING"
|
||||
exit 1
|
||||
fi
|
||||
Loading…
x
Reference in New Issue
Block a user