515 lines
18 KiB
Bash
Executable File
515 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# AionUi WebUI — One-Click Installation Script
|
|
# ============================================================================
|
|
# Usage:
|
|
# curl -fsSL https://raw.githubusercontent.com/iOfficeAI/AionUi/main/scripts/install-web.sh | bash
|
|
# # Or specify version:
|
|
# VERSION=1.0.0 bash install-web.sh
|
|
# # Or install to custom directory:
|
|
# INSTALL_DIR=/opt/aionui-web bash install-web.sh
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# ─── Default Configuration ──────────────────────────────────────────────────
|
|
VERSION="${VERSION:-__VERSION__}"
|
|
# Note: CI runs `sed "s/__VERSION__/<ver>/g"` on this file, replacing both
|
|
# occurrences above into e.g. "1.9.19". The resolve_version() function uses a
|
|
# regex-based check (looks for letters) to detect the unreplaced placeholder,
|
|
# so never add a literal "__VERSION__" string to any comparison below.
|
|
INSTALL_DIR="${INSTALL_DIR:-${HOME}/.local/share/aionui-web}"
|
|
BIN_DIR="${BIN_DIR:-${HOME}/.local/bin}"
|
|
MIRROR="${MIRROR:-https://github.com/iOfficeAI/AionUi/releases/download}"
|
|
CREATE_SYMLINK="${CREATE_SYMLINK:-1}"
|
|
UPDATE_PATH="${UPDATE_PATH:-1}"
|
|
|
|
# ─── Color Definitions ──────────────────────────────────────────────────────
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# ─── Helper Functions ───────────────────────────────────────────────────────
|
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
success() { echo -e "${GREEN}[✓]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
error() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
|
die() { error "$*"; exit 1; }
|
|
|
|
banner() {
|
|
echo -e "${CYAN}${BOLD}"
|
|
echo " ╔══════════════════════════════════════════════╗"
|
|
echo " ║ AionUi WebUI Installer (No Electron) ║"
|
|
echo " ╚══════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
}
|
|
|
|
# ─── Parse Command-Line Arguments ───────────────────────────────────────────
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--version)
|
|
VERSION="$2"
|
|
shift 2
|
|
;;
|
|
--mirror)
|
|
MIRROR="$2"
|
|
shift 2
|
|
;;
|
|
--install-dir)
|
|
INSTALL_DIR="$2"
|
|
shift 2
|
|
;;
|
|
--no-symlink)
|
|
CREATE_SYMLINK=0
|
|
shift
|
|
;;
|
|
--no-path)
|
|
UPDATE_PATH=0
|
|
shift
|
|
;;
|
|
--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
warn "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Usage: install-web.sh [OPTIONS]
|
|
|
|
Options:
|
|
--version <version> Specify version to install (default: latest or CI-embedded)
|
|
--mirror <url> Specify mirror URL (default: GitHub releases)
|
|
--install-dir <path> Specify installation directory (default: ~/.local/share/aionui-web)
|
|
--no-symlink Do not create symlink in ~/.local/bin
|
|
--no-path Do not add PATH to shell profile
|
|
--help Show this help message
|
|
|
|
Environment Variables:
|
|
VERSION Version to install (same as --version)
|
|
INSTALL_DIR Installation directory (same as --install-dir)
|
|
MIRROR Mirror URL (same as --mirror)
|
|
|
|
Examples:
|
|
# Install latest version
|
|
curl -fsSL https://raw.githubusercontent.com/iOfficeAI/AionUi/main/scripts/install-web.sh | bash
|
|
|
|
# Install specific version
|
|
VERSION=1.0.0 bash install-web.sh
|
|
|
|
# Install to custom directory
|
|
INSTALL_DIR=/opt/aionui-web bash install-web.sh
|
|
|
|
# Use local file mirror (for offline installation)
|
|
MIRROR=file:///path/to/releases bash install-web.sh
|
|
EOF
|
|
}
|
|
|
|
# ─── Core Functions ────────────────────────────────────────────────────────
|
|
detect_platform_arch() {
|
|
local os_type="$(uname -s)"
|
|
local machine="$(uname -m)"
|
|
|
|
# Map OS type
|
|
case "$os_type" in
|
|
Darwin)
|
|
PLATFORM="darwin"
|
|
;;
|
|
Linux)
|
|
PLATFORM="linux"
|
|
;;
|
|
MINGW*|MSYS*|CYGWIN*)
|
|
PLATFORM="win"
|
|
;;
|
|
*)
|
|
die "Unsupported OS: $os_type (only Darwin, Linux, Windows supported)"
|
|
;;
|
|
esac
|
|
|
|
# Map architecture
|
|
case "$machine" in
|
|
x86_64|amd64)
|
|
ARCH="x86_64"
|
|
;;
|
|
aarch64|arm64)
|
|
ARCH="arm64"
|
|
;;
|
|
*)
|
|
die "Unsupported architecture: $machine (only x86_64/amd64 and aarch64/arm64 supported)"
|
|
;;
|
|
esac
|
|
|
|
info "Detected platform: ${BOLD}${PLATFORM}-${ARCH}${NC}"
|
|
|
|
# Build tarball filename
|
|
TARBALL_NAME="aionui-web-${VERSION}-${PLATFORM}-${ARCH}.tar.gz"
|
|
CHECKSUM_NAME="${TARBALL_NAME}.sha256"
|
|
}
|
|
|
|
resolve_version() {
|
|
# Trigger GitHub API resolution when:
|
|
# - VERSION is "latest" (explicit)
|
|
# - VERSION still contains the CI placeholder pattern (letters/underscores,
|
|
# i.e. sed did NOT run and we have the raw "__VERSION__" token)
|
|
# Note: a real version number is digits+dots only, so `[a-zA-Z_]` is a
|
|
# reliable marker of "placeholder". We avoid literal "__VERSION__" here
|
|
# because the CI sed replacement rewrites every occurrence in this file,
|
|
# including the comparison string.
|
|
if [[ "$VERSION" == "latest" || "$VERSION" =~ [a-zA-Z_] ]]; then
|
|
info "Resolving latest version from GitHub API..."
|
|
|
|
if command -v curl &>/dev/null; then
|
|
VERSION=$(curl -fsSL "https://api.github.com/repos/iOfficeAI/AionUi/releases/latest" \
|
|
| grep '"tag_name"' | head -1 | sed 's/.*"v\([^"]*\)".*/\1/')
|
|
elif command -v wget &>/dev/null; then
|
|
VERSION=$(wget -qO- "https://api.github.com/repos/iOfficeAI/AionUi/releases/latest" \
|
|
| grep '"tag_name"' | head -1 | sed 's/.*"v\([^"]*\)".*/\1/')
|
|
else
|
|
die "curl or wget is required to resolve version. Please install curl or wget."
|
|
fi
|
|
|
|
if [[ -z "$VERSION" ]]; then
|
|
die "Failed to resolve latest version. Please specify version manually: VERSION=1.0.0 bash $0"
|
|
fi
|
|
|
|
info "Latest version: ${BOLD}v${VERSION}${NC}"
|
|
else
|
|
info "Using specified version: ${BOLD}v${VERSION}${NC}"
|
|
fi
|
|
|
|
# Rebuild tarball name (VERSION may have changed)
|
|
TARBALL_NAME="aionui-web-${VERSION}-${PLATFORM}-${ARCH}.tar.gz"
|
|
CHECKSUM_NAME="${TARBALL_NAME}.sha256"
|
|
}
|
|
|
|
download_tarball() {
|
|
# Create temp directory
|
|
TEMP_DIR="$(mktemp -d)"
|
|
TARBALL_PATH="${TEMP_DIR}/${TARBALL_NAME}"
|
|
CHECKSUM_PATH="${TEMP_DIR}/${CHECKSUM_NAME}"
|
|
|
|
# Build download URL
|
|
# MIRROR formats:
|
|
# - GitHub: https://github.com/iOfficeAI/AionUi/releases/download
|
|
# - file: file:///path/to/releases
|
|
if [[ "$MIRROR" == file://* ]]; then
|
|
# Local file mirror (for offline installation or testing)
|
|
local base_path="${MIRROR#file://}"
|
|
TARBALL_URL="file://${base_path}/v${VERSION}/${TARBALL_NAME}"
|
|
CHECKSUM_URL="file://${base_path}/v${VERSION}/${CHECKSUM_NAME}"
|
|
else
|
|
# GitHub releases
|
|
TARBALL_URL="${MIRROR}/v${VERSION}/${TARBALL_NAME}"
|
|
CHECKSUM_URL="${MIRROR}/v${VERSION}/${CHECKSUM_NAME}"
|
|
fi
|
|
|
|
info "Downloading ${BOLD}${TARBALL_NAME}${NC}..."
|
|
info "URL: $TARBALL_URL"
|
|
|
|
# Download tarball
|
|
if [[ "$TARBALL_URL" == file://* ]]; then
|
|
# Local file: copy directly
|
|
local src_path="${TARBALL_URL#file://}"
|
|
if [[ ! -f "$src_path" ]]; then
|
|
die "Tarball not found at local mirror: $src_path"
|
|
fi
|
|
cp "$src_path" "$TARBALL_PATH"
|
|
else
|
|
# Remote file: use curl or wget
|
|
if command -v curl &>/dev/null; then
|
|
curl -fSL --progress-bar -o "$TARBALL_PATH" "$TARBALL_URL" || die "Download failed"
|
|
elif command -v wget &>/dev/null; then
|
|
wget --show-progress -q -O "$TARBALL_PATH" "$TARBALL_URL" || die "Download failed"
|
|
else
|
|
die "curl or wget is required. Please install curl or wget."
|
|
fi
|
|
fi
|
|
|
|
local size
|
|
size=$(du -h "$TARBALL_PATH" | cut -f1)
|
|
success "Downloaded tarball ($size)"
|
|
|
|
# Download SHA256 checksum
|
|
info "Downloading ${BOLD}${CHECKSUM_NAME}${NC}..."
|
|
if [[ "$CHECKSUM_URL" == file://* ]]; then
|
|
local src_path="${CHECKSUM_URL#file://}"
|
|
if [[ ! -f "$src_path" ]]; then
|
|
die "Checksum file not found at local mirror: $src_path"
|
|
fi
|
|
cp "$src_path" "$CHECKSUM_PATH"
|
|
else
|
|
if command -v curl &>/dev/null; then
|
|
curl -fSL -o "$CHECKSUM_PATH" "$CHECKSUM_URL" || die "Checksum download failed"
|
|
elif command -v wget &>/dev/null; then
|
|
wget -q -O "$CHECKSUM_PATH" "$CHECKSUM_URL" || die "Checksum download failed"
|
|
fi
|
|
fi
|
|
|
|
success "Downloaded checksum"
|
|
}
|
|
|
|
verify_checksum() {
|
|
info "Verifying SHA256 checksum..."
|
|
|
|
# Read expected checksum (from .sha256 file)
|
|
local expected_checksum
|
|
expected_checksum=$(awk '{print $1}' "$CHECKSUM_PATH")
|
|
|
|
if [[ -z "$expected_checksum" ]]; then
|
|
die "Failed to read checksum from $CHECKSUM_NAME"
|
|
fi
|
|
|
|
# Calculate actual checksum
|
|
local actual_checksum
|
|
if command -v shasum &>/dev/null; then
|
|
actual_checksum=$(shasum -a 256 "$TARBALL_PATH" | awk '{print $1}')
|
|
elif command -v sha256sum &>/dev/null; then
|
|
actual_checksum=$(sha256sum "$TARBALL_PATH" | awk '{print $1}')
|
|
else
|
|
warn "shasum/sha256sum not found, skipping checksum verification"
|
|
return
|
|
fi
|
|
|
|
if [[ "$actual_checksum" != "$expected_checksum" ]]; then
|
|
error "Checksum mismatch!"
|
|
error "Expected: $expected_checksum"
|
|
error "Actual: $actual_checksum"
|
|
die "Tarball may be corrupted. Please try again."
|
|
fi
|
|
|
|
success "Checksum verified: ${expected_checksum:0:16}..."
|
|
}
|
|
|
|
extract_tarball() {
|
|
info "Installing to ${BOLD}${INSTALL_DIR}${NC}..."
|
|
|
|
# If installation directory exists, backup old version
|
|
if [[ -d "$INSTALL_DIR" ]]; then
|
|
local backup_dir="${INSTALL_DIR}.backup.$(date +%s)"
|
|
warn "Installation directory exists, creating backup: $backup_dir"
|
|
mv "$INSTALL_DIR" "$backup_dir"
|
|
fi
|
|
|
|
# Create parent directory of installation directory
|
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
|
|
# Extract tarball
|
|
# Tarball root directory is aionui-web/, rename after extraction to INSTALL_DIR
|
|
local extract_temp="${TEMP_DIR}/extract"
|
|
mkdir -p "$extract_temp"
|
|
|
|
info "Extracting tarball..."
|
|
tar -xzf "$TARBALL_PATH" -C "$extract_temp" || die "Failed to extract tarball"
|
|
|
|
# Move to final installation location
|
|
if [[ -d "${extract_temp}/aionui-web" ]]; then
|
|
mv "${extract_temp}/aionui-web" "$INSTALL_DIR"
|
|
else
|
|
die "Tarball structure is invalid (missing aionui-web/ directory)"
|
|
fi
|
|
|
|
success "Extracted to $INSTALL_DIR"
|
|
|
|
# Set executable permission on the bun-compiled standalone binary
|
|
chmod +x "${INSTALL_DIR}/aionui-web" 2>/dev/null || true
|
|
|
|
# On macOS, strip the quarantine xattr Safari/Chrome/curl-downloaded files
|
|
# inherit — otherwise Gatekeeper kills unsigned Mach-O binaries with a
|
|
# "damaged, can't be opened" dialog. This is standard practice for CLI
|
|
# tools distributed as tarballs (bun, deno, rustup do the same).
|
|
if command -v xattr &>/dev/null; then
|
|
xattr -dr com.apple.quarantine "${INSTALL_DIR}" 2>/dev/null || true
|
|
fi
|
|
|
|
# Verify installation
|
|
if [[ ! -x "${INSTALL_DIR}/aionui-web" ]]; then
|
|
die "Installation failed: ${INSTALL_DIR}/aionui-web not found or not executable"
|
|
fi
|
|
|
|
success "Installation completed"
|
|
|
|
# Clean up temporary files
|
|
rm -rf "$TEMP_DIR"
|
|
}
|
|
|
|
create_symlink() {
|
|
local symlink_path="${BIN_DIR}/aionui-web"
|
|
local target_path="${INSTALL_DIR}/aionui-web"
|
|
|
|
info "Creating symlink: ${BOLD}${symlink_path}${NC} -> ${target_path}"
|
|
|
|
# Create BIN_DIR if not exists
|
|
mkdir -p "$BIN_DIR"
|
|
|
|
# If symlink already exists, remove old symlink
|
|
if [[ -L "$symlink_path" ]]; then
|
|
warn "Symlink already exists, removing old symlink"
|
|
rm "$symlink_path"
|
|
elif [[ -e "$symlink_path" ]]; then
|
|
die "File already exists at $symlink_path (not a symlink). Please remove it manually."
|
|
fi
|
|
|
|
# Create symlink
|
|
ln -s "$target_path" "$symlink_path" || die "Failed to create symlink"
|
|
|
|
success "Symlink created: $symlink_path"
|
|
}
|
|
|
|
update_shell_profile() {
|
|
# Check if BIN_DIR is already in PATH
|
|
if [[ ":$PATH:" == *":${BIN_DIR}:"* ]]; then
|
|
info "PATH already contains ${BOLD}${BIN_DIR}${NC}"
|
|
return
|
|
fi
|
|
|
|
info "Adding ${BOLD}${BIN_DIR}${NC} to PATH in shell profile..."
|
|
|
|
# Detect current shell
|
|
local shell_name
|
|
shell_name="$(basename "$SHELL")"
|
|
|
|
local profile_file=""
|
|
case "$shell_name" in
|
|
bash)
|
|
if [[ -f "$HOME/.bashrc" ]]; then
|
|
profile_file="$HOME/.bashrc"
|
|
elif [[ -f "$HOME/.bash_profile" ]]; then
|
|
profile_file="$HOME/.bash_profile"
|
|
fi
|
|
;;
|
|
zsh)
|
|
profile_file="$HOME/.zshrc"
|
|
;;
|
|
fish)
|
|
profile_file="$HOME/.config/fish/config.fish"
|
|
;;
|
|
*)
|
|
warn "Unknown shell: $shell_name. Please manually add ${BIN_DIR} to PATH."
|
|
return
|
|
;;
|
|
esac
|
|
|
|
if [[ -z "$profile_file" ]]; then
|
|
warn "Shell profile not found. Please manually add ${BIN_DIR} to PATH."
|
|
return
|
|
fi
|
|
|
|
# Add PATH configuration
|
|
local path_line="export PATH=\"${BIN_DIR}:\$PATH\""
|
|
|
|
# Check if configuration already exists
|
|
if grep -q "${BIN_DIR}" "$profile_file" 2>/dev/null; then
|
|
info "PATH configuration already exists in $profile_file"
|
|
return
|
|
fi
|
|
|
|
# Add to profile
|
|
echo "" >> "$profile_file"
|
|
echo "# Added by aionui-web installer" >> "$profile_file"
|
|
echo "$path_line" >> "$profile_file"
|
|
|
|
success "Added PATH to $profile_file"
|
|
warn "Please restart your shell or run: source $profile_file"
|
|
}
|
|
|
|
print_summary() {
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}══════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN}${BOLD} 🎉 AionUi WebUI v${VERSION} Installed!${NC}"
|
|
echo -e "${GREEN}${BOLD}══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}📍 Installation directory:${NC} ${INSTALL_DIR}"
|
|
if [[ "$CREATE_SYMLINK" == "1" ]]; then
|
|
echo -e " ${BOLD}📍 Symlink:${NC} ${BIN_DIR}/aionui-web"
|
|
fi
|
|
echo ""
|
|
echo -e " ${BOLD}🚀 Usage:${NC}"
|
|
echo ""
|
|
if [[ "$CREATE_SYMLINK" == "1" && ":$PATH:" == *":${BIN_DIR}:"* ]]; then
|
|
echo " # Start AionUi WebUI"
|
|
echo " aionui-web start"
|
|
echo ""
|
|
echo " # Check version"
|
|
echo " aionui-web version"
|
|
else
|
|
echo " # Start AionUi WebUI (using full path)"
|
|
echo " ${INSTALL_DIR}/aionui-web start"
|
|
echo ""
|
|
echo " # Or add symlink to PATH:"
|
|
if [[ "$CREATE_SYMLINK" == "1" ]]; then
|
|
echo " export PATH=\"${BIN_DIR}:\$PATH\""
|
|
else
|
|
echo " ln -s ${INSTALL_DIR}/aionui-web ~/.local/bin/aionui-web"
|
|
echo " export PATH=\"~/.local/bin:\$PATH\""
|
|
fi
|
|
fi
|
|
echo ""
|
|
echo -e " ${BOLD}📖 Documentation:${NC} https://github.com/iOfficeAI/AionUi"
|
|
echo -e " ${BOLD}🐛 Report issues:${NC} https://github.com/iOfficeAI/AionUi/issues"
|
|
echo ""
|
|
echo -e " ${BOLD}🗑️ Uninstall:${NC}"
|
|
echo ""
|
|
echo " # Remove installation directory"
|
|
echo " rm -rf ${INSTALL_DIR}"
|
|
if [[ "$CREATE_SYMLINK" == "1" ]]; then
|
|
echo ""
|
|
echo " # Remove symlink"
|
|
echo " rm ${BIN_DIR}/aionui-web"
|
|
fi
|
|
if [[ "$UPDATE_PATH" == "1" ]]; then
|
|
echo ""
|
|
echo " # Remove PATH configuration from shell profile"
|
|
echo " # (manually edit ~/.bashrc or ~/.zshrc)"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# ─── Main Flow ──────────────────────────────────────────────────────────────
|
|
main() {
|
|
banner
|
|
parse_args "$@"
|
|
|
|
# Step 1: Detect platform and architecture
|
|
detect_platform_arch
|
|
|
|
# Step 2: Resolve version (if VERSION is __VERSION__ or latest)
|
|
resolve_version
|
|
|
|
# Step 3: Download tarball
|
|
download_tarball
|
|
|
|
# Step 4: Verify SHA256 checksum
|
|
verify_checksum
|
|
|
|
# Step 5: Extract tarball
|
|
extract_tarball
|
|
|
|
# Step 6: Create symlink
|
|
if [[ "$CREATE_SYMLINK" == "1" ]]; then
|
|
create_symlink
|
|
fi
|
|
|
|
# Step 7: Update shell profile PATH
|
|
if [[ "$UPDATE_PATH" == "1" ]]; then
|
|
update_shell_profile
|
|
fi
|
|
|
|
# Step 8: Print summary
|
|
print_summary
|
|
}
|
|
|
|
# Execute
|
|
main "$@"
|