Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
879 lines
25 KiB
Bash
Executable File
879 lines
25 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# syntaxis Bundle Installer
|
||
#
|
||
# Standalone bash installer for offline bundles
|
||
# Works without NuShell or Rust toolchain
|
||
#
|
||
# Usage:
|
||
# ./install.sh # Interactive mode
|
||
# ./install.sh --prefix ~/.local # Custom prefix
|
||
# ./install.sh --verify --force # Auto-verify and force overwrite
|
||
# ./install.sh --help # Show help
|
||
|
||
set -euo pipefail
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Script state
|
||
BUNDLE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
SCRIPT_DIR="$(pwd)"
|
||
BUNDLE_NAME=$(basename "$BUNDLE_DIR")
|
||
INSTALL_PREFIX=""
|
||
VERIFY=false
|
||
FORCE=false
|
||
BACKUP=true
|
||
DRY_RUN=false
|
||
HELP=false
|
||
UNATTENDED=false
|
||
|
||
# Defaults by OS
|
||
detect_os() {
|
||
case "$(uname -s)" in
|
||
Darwin*) echo "macos" ;;
|
||
Linux*) echo "linux" ;;
|
||
MINGW*) echo "windows" ;;
|
||
*) echo "unknown" ;;
|
||
esac
|
||
}
|
||
|
||
detect_arch() {
|
||
case "$(uname -m)" in
|
||
x86_64) echo "x86_64" ;;
|
||
aarch64) echo "aarch64" ;;
|
||
arm64) echo "aarch64" ;; # macOS Apple Silicon
|
||
*) echo "unknown" ;;
|
||
esac
|
||
}
|
||
|
||
# Logging functions
|
||
log() {
|
||
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $*" >&2
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}✅${NC} $*"
|
||
}
|
||
|
||
log_info() {
|
||
echo -e "${BLUE}ℹ️${NC} $*"
|
||
}
|
||
|
||
log_warn() {
|
||
echo -e "${YELLOW}⚠️${NC} $*"
|
||
}
|
||
|
||
# Check if NuShell is installed
|
||
check_nushell() {
|
||
if command -v nu &> /dev/null; then
|
||
NUSHELL_VERSION=$(nu --version 2>/dev/null | head -1)
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Show NuShell installation guide
|
||
show_nushell_guide() {
|
||
local os=$(detect_os)
|
||
|
||
echo ""
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE}📦 How to Install NuShell${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
|
||
case "$os" in
|
||
macos)
|
||
echo "macOS (Homebrew):"
|
||
echo " brew install nushell"
|
||
echo ""
|
||
echo "macOS (Cargo):"
|
||
echo " cargo install nu"
|
||
;;
|
||
linux)
|
||
echo "Linux (Arch):"
|
||
echo " pacman -S nushell"
|
||
echo ""
|
||
echo "Linux (Ubuntu/Debian):"
|
||
echo " cargo install nu"
|
||
echo ""
|
||
echo "Linux (Fedora):"
|
||
echo " dnf install nushell"
|
||
;;
|
||
windows)
|
||
echo "Windows (WinGet):"
|
||
echo " winget install nushell"
|
||
echo ""
|
||
echo "Windows (Cargo):"
|
||
echo " cargo install nu"
|
||
;;
|
||
*)
|
||
echo "See installation guide:"
|
||
echo " https://www.nushell.sh/book/installation.html"
|
||
;;
|
||
esac
|
||
|
||
echo ""
|
||
echo "After installation:"
|
||
echo " 1. Re-run this installer to use full-featured wrappers"
|
||
echo " 2. Run: ./install.sh"
|
||
echo ""
|
||
}
|
||
|
||
# Prompt user about NuShell
|
||
prompt_nushell() {
|
||
if [[ $UNATTENDED == true ]]; then
|
||
# In unattended mode, just warn and continue
|
||
log_warn "NuShell not found - using simplified wrapper mode"
|
||
return 0
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${YELLOW}⚠️ NuShell Not Detected${NC}"
|
||
echo ""
|
||
echo "Wrappers will use simplified mode (config auto-discovery only)"
|
||
echo "For full features, install NuShell: https://www.nushell.sh"
|
||
echo ""
|
||
|
||
while true; do
|
||
echo -n "Continue with simplified wrappers? [Y/n/i] "
|
||
read -r response
|
||
case "$response" in
|
||
[Yy]|"")
|
||
return 0
|
||
;;
|
||
[Nn])
|
||
log_info "Please install NuShell and run this installer again"
|
||
show_nushell_guide
|
||
exit 0
|
||
;;
|
||
[Ii])
|
||
show_nushell_guide
|
||
echo -n "Continue with installation? [Y/n] "
|
||
read -r cont
|
||
if [[ "$cont" =~ [Yy] ]] || [[ -z "$cont" ]]; then
|
||
return 0
|
||
else
|
||
exit 0
|
||
fi
|
||
;;
|
||
*)
|
||
echo "Please answer Y (continue), N (exit), or I (installation guide)"
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Display help
|
||
show_help() {
|
||
cat << 'EOF'
|
||
syntaxis Bundle Installer
|
||
|
||
USAGE:
|
||
./install.sh [OPTIONS]
|
||
|
||
OPTIONS:
|
||
--prefix <PATH> Installation prefix
|
||
Default: /usr/local (Linux/macOS) or /Program Files/syntaxis (Windows)
|
||
--config-dir <PATH> Configuration directory
|
||
Default: ~/.config/syntaxis
|
||
--verify Verify checksums before installing
|
||
--backup Backup existing binaries (default: true)
|
||
--no-backup Don't backup existing binaries
|
||
--force Force overwrite without prompting
|
||
--unattended Non-interactive mode (use defaults)
|
||
--dry-run Show what would be installed
|
||
--help Show this help message
|
||
|
||
EXAMPLES:
|
||
# Interactive installation (prompts for options)
|
||
./install.sh
|
||
|
||
# Install to custom location with verification
|
||
./install.sh --prefix ~/.local --verify
|
||
|
||
# Force installation without backups
|
||
./install.sh --force --no-backup
|
||
|
||
# Unattended installation
|
||
./install.sh --unattended --prefix /opt/syntaxis
|
||
|
||
# Preview installation
|
||
./install.sh --dry-run
|
||
|
||
NOTES:
|
||
- This script should be run from within the extracted bundle directory
|
||
- Bundles include all necessary files: binaries, configs, and documentation
|
||
- Installation manifests are saved to ~/.syntaxis/
|
||
- Existing binaries are backed up with timestamp suffix
|
||
EOF
|
||
}
|
||
|
||
# Parse command-line arguments
|
||
parse_args() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--prefix)
|
||
INSTALL_PREFIX="$2"
|
||
shift 2
|
||
;;
|
||
--config-dir)
|
||
CONFIG_DIR="$2"
|
||
shift 2
|
||
;;
|
||
--verify)
|
||
VERIFY=true
|
||
shift
|
||
;;
|
||
--backup)
|
||
BACKUP=true
|
||
shift
|
||
;;
|
||
--no-backup)
|
||
BACKUP=false
|
||
shift
|
||
;;
|
||
--force)
|
||
FORCE=true
|
||
shift
|
||
;;
|
||
--unattended)
|
||
UNATTENDED=true
|
||
shift
|
||
;;
|
||
--dry-run)
|
||
DRY_RUN=true
|
||
shift
|
||
;;
|
||
--help|-h)
|
||
HELP=true
|
||
shift
|
||
;;
|
||
*)
|
||
log_error "Unknown option: $1"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Validate bundle structure
|
||
validate_bundle() {
|
||
log_info "Validating bundle structure..."
|
||
|
||
local required_dirs=("bin" "configs" "docs")
|
||
local missing_dirs=()
|
||
|
||
for dir in "${required_dirs[@]}"; do
|
||
if [[ ! -d "$BUNDLE_DIR/$dir" ]]; then
|
||
missing_dirs+=("$dir")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#missing_dirs[@]} -gt 0 ]]; then
|
||
log_error "Missing required directories: ${missing_dirs[*]}"
|
||
return 1
|
||
fi
|
||
|
||
# Check for manifest
|
||
if [[ ! -f "$BUNDLE_DIR/manifest.toml" ]]; then
|
||
log_warn "manifest.toml not found (may affect verification)"
|
||
fi
|
||
|
||
log_success "Bundle structure valid"
|
||
return 0
|
||
}
|
||
|
||
# Calculate SHA256 checksum
|
||
sha256_file() {
|
||
local file="$1"
|
||
if command -v sha256sum &> /dev/null; then
|
||
sha256sum "$file" | awk '{print $1}'
|
||
elif command -v shasum &> /dev/null; then
|
||
shasum -a 256 "$file" | awk '{print $1}'
|
||
elif command -v openssl &> /dev/null; then
|
||
openssl dgst -sha256 "$file" | awk '{print $NF}'
|
||
else
|
||
log_warn "No checksum command available, skipping verification"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Verify checksums from manifest
|
||
verify_checksums() {
|
||
if [[ ! -f "$BUNDLE_DIR/manifest.toml" ]]; then
|
||
log_warn "manifest.toml not found, skipping checksum verification"
|
||
return 0
|
||
fi
|
||
|
||
log_info "Verifying checksums..."
|
||
|
||
local errors=0
|
||
local verified=0
|
||
|
||
# Parse checksums from TOML (basic parsing)
|
||
while IFS='=' read -r key value; do
|
||
if [[ $key =~ ^[[:space:]]*\"bin/ ]]; then
|
||
local file_path=$(echo "$key" | sed 's/.*"\(bin\/[^"]*\)".*/\1/')
|
||
local expected=$(echo "$value" | sed 's/.*"\([^"]*\)".*/\1/')
|
||
|
||
if [[ -f "$BUNDLE_DIR/$file_path" ]]; then
|
||
local actual=$(sha256_file "$BUNDLE_DIR/$file_path")
|
||
if [[ "$actual" == "$expected" ]]; then
|
||
verified=$((verified + 1))
|
||
else
|
||
log_warn "Checksum mismatch: $file_path"
|
||
errors=$((errors + 1))
|
||
fi
|
||
fi
|
||
fi
|
||
done < "$BUNDLE_DIR/manifest.toml"
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
log_warn "Checksum verification failed for $errors file(s)"
|
||
return 1
|
||
fi
|
||
|
||
if [[ $verified -gt 0 ]]; then
|
||
log_success "Verified $verified file(s) with checksums"
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# Detect installation prefix
|
||
detect_install_prefix() {
|
||
local os=$(detect_os)
|
||
local default_prefix
|
||
|
||
case "$os" in
|
||
linux|macos)
|
||
# Try /usr/local first, fallback to ~/.local
|
||
if [[ -w /usr/local/bin ]]; then
|
||
default_prefix="/usr/local"
|
||
else
|
||
default_prefix="$HOME/.local"
|
||
fi
|
||
;;
|
||
windows)
|
||
default_prefix="C:\\Program Files\\syntaxis"
|
||
;;
|
||
*)
|
||
default_prefix="$HOME/.local"
|
||
;;
|
||
esac
|
||
|
||
if [[ -z "$INSTALL_PREFIX" ]]; then
|
||
INSTALL_PREFIX="$default_prefix"
|
||
fi
|
||
|
||
# Expand ~ to home
|
||
INSTALL_PREFIX=$(eval echo "$INSTALL_PREFIX")
|
||
|
||
# Create bin directory path
|
||
BIN_DIR="$INSTALL_PREFIX/bin"
|
||
}
|
||
|
||
# Ensure directory exists and is writable
|
||
ensure_directory() {
|
||
local dir="$1"
|
||
local description="$2"
|
||
|
||
if [[ ! -d "$dir" ]]; then
|
||
log_info "Creating $description: $dir"
|
||
mkdir -p "$dir" || {
|
||
log_error "Failed to create $description: $dir"
|
||
return 1
|
||
}
|
||
fi
|
||
|
||
# Check write permissions
|
||
if [[ ! -w "$dir" ]]; then
|
||
log_error "No write permission to $description: $dir"
|
||
return 1
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# Backup existing binary
|
||
backup_binary() {
|
||
local binary_path="$1"
|
||
|
||
if [[ ! -f "$binary_path" ]]; then
|
||
return 0 # No backup needed if file doesn't exist
|
||
fi
|
||
|
||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||
local backup_path="${binary_path}.backup.${timestamp}"
|
||
|
||
log_info "Backing up: $(basename "$binary_path") → $(basename "$backup_path")"
|
||
cp "$binary_path" "$backup_path" || {
|
||
log_error "Failed to backup $binary_path"
|
||
return 1
|
||
}
|
||
|
||
return 0
|
||
}
|
||
|
||
# Create hybrid bash wrapper script for a binary
|
||
# Tries NuShell first (full features), falls back to bash (simplified config discovery)
|
||
create_bash_wrapper() {
|
||
local dest="$1"
|
||
local binary_name=$(basename "$dest")
|
||
|
||
cat > "$dest" << 'WRAPPER_EOF'
|
||
#!/bin/bash
|
||
# Hybrid wrapper for BINARY_NAME
|
||
# Layer 1: Bash wrapper
|
||
# - Tries NuShell first (full features with libraries)
|
||
# - Falls back to bash (simplified config discovery)
|
||
#
|
||
# Usage: BINARY_NAME [arguments]
|
||
|
||
BINARY_NAME="BINARY_PLACEHOLDER"
|
||
REAL_BINARY="$(dirname "$0")/${BINARY_NAME}.real"
|
||
|
||
# Try NuShell first (preferred - full features)
|
||
if command -v nu &>/dev/null; then
|
||
# NuShell available - use full wrapper with libraries
|
||
export NU_LIB_DIRS="$HOME/.config/syntaxis/scripts"
|
||
exec nu "$HOME/.config/syntaxis/scripts/${BINARY_NAME}.nu" "$@"
|
||
fi
|
||
|
||
# Bash fallback (simplified - config discovery only)
|
||
# Search for config file in standard locations
|
||
for cfg_path in \
|
||
"$HOME/.config/syntaxis/${BINARY_NAME}.toml" \
|
||
"$HOME/.config/syntaxis/config.toml" \
|
||
"$HOME/.config/syntaxis/syntaxis.toml" \
|
||
".syntaxis/${BINARY_NAME}.toml" \
|
||
".project/${BINARY_NAME}.toml" \
|
||
".coder/${BINARY_NAME}.toml"; do
|
||
|
||
if [[ -f "$cfg_path" ]]; then
|
||
exec "$REAL_BINARY" --config "$cfg_path" "$@"
|
||
fi
|
||
done
|
||
|
||
# No config found - execute with defaults
|
||
exec "$REAL_BINARY" "$@"
|
||
WRAPPER_EOF
|
||
|
||
# Replace placeholder with actual binary name
|
||
sed -i.tmp "s/BINARY_PLACEHOLDER/${binary_name}/g" "$dest"
|
||
rm -f "${dest}.tmp"
|
||
|
||
chmod 755 "$dest"
|
||
}
|
||
|
||
# Install binary with wrapper support
|
||
install_binary() {
|
||
local source="$1"
|
||
local dest="$2"
|
||
local binary_name=$(basename "$source")
|
||
local real_dest="${dest}.real"
|
||
|
||
if [[ ! -f "$source" ]]; then
|
||
log_warn "Binary not found: $binary_name"
|
||
return 1
|
||
fi
|
||
|
||
# Check if destination exists
|
||
if [[ -f "$dest" ]] && [[ $FORCE == false ]] && [[ $UNATTENDED == false ]]; then
|
||
echo -n "Binary already exists: $binary_name. Overwrite? (y/n) "
|
||
read -r response
|
||
if [[ $response != [yY] ]]; then
|
||
log_info "Skipped: $binary_name"
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# Backup if requested
|
||
if [[ $BACKUP == true ]]; then
|
||
backup_binary "$dest" || true # Don't fail if no backup
|
||
backup_binary "$real_dest" || true # Also backup .real if it exists
|
||
fi
|
||
|
||
# Copy binary to .real
|
||
cp "$source" "$real_dest" || {
|
||
log_error "Failed to install: $binary_name"
|
||
return 1
|
||
}
|
||
|
||
# Make .real binary executable (Unix)
|
||
if [[ $(uname -s) != "MINGW"* ]]; then
|
||
chmod 755 "$real_dest"
|
||
fi
|
||
|
||
# Create bash wrapper at original location
|
||
create_bash_wrapper "$dest"
|
||
|
||
log_success "Installed: $binary_name (with wrapper)"
|
||
log_info " Real binary: $real_dest"
|
||
log_info " Wrapper: $dest"
|
||
return 0
|
||
}
|
||
|
||
# Install all binaries
|
||
install_binaries() {
|
||
log_info "Installing binaries..."
|
||
|
||
if [[ ! -d "$BUNDLE_DIR/bin" ]]; then
|
||
log_error "No bin directory found in bundle"
|
||
return 1
|
||
fi
|
||
|
||
local binaries=()
|
||
local failed=0
|
||
|
||
# Find all binaries
|
||
for binary in "$BUNDLE_DIR/bin"/*; do
|
||
if [[ -f "$binary" ]] && [[ -x "$binary" ]]; then
|
||
binaries+=("$(basename "$binary")")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#binaries[@]} -eq 0 ]]; then
|
||
log_error "No binaries found in bundle/bin"
|
||
return 1
|
||
fi
|
||
|
||
# Install each binary
|
||
for binary in "${binaries[@]}"; do
|
||
if ! install_binary "$BUNDLE_DIR/bin/$binary" "$BIN_DIR/$binary"; then
|
||
failed=$((failed + 1))
|
||
fi
|
||
done
|
||
|
||
if [[ $failed -gt 0 ]]; then
|
||
log_error "$failed binary(ies) failed to install"
|
||
return 1
|
||
fi
|
||
|
||
log_success "All binaries installed (${#binaries[@]} total)"
|
||
return 0
|
||
}
|
||
|
||
# Deploy NuShell wrapper scripts
|
||
deploy_wrapper_scripts() {
|
||
local scripts_dir="$BUNDLE_DIR/scripts"
|
||
local config_scripts_dir="$HOME/.config/syntaxis/scripts"
|
||
|
||
# Check if scripts directory exists in bundle
|
||
if [[ ! -d "$scripts_dir" ]]; then
|
||
log_warn "No scripts directory found in bundle (wrapper scripts not available)"
|
||
return 0
|
||
fi
|
||
|
||
log_info "Deploying wrapper scripts to $config_scripts_dir..."
|
||
|
||
# Create wrapper scripts directory
|
||
if ! ensure_directory "$config_scripts_dir" "wrapper scripts directory"; then
|
||
log_warn "Could not create wrapper scripts directory"
|
||
return 0
|
||
fi
|
||
|
||
local deployed=0
|
||
local failed=0
|
||
|
||
# Deploy shared library
|
||
if [[ -f "$scripts_dir/syntaxis-lib.nu" ]]; then
|
||
cp "$scripts_dir/syntaxis-lib.nu" "$config_scripts_dir/" || {
|
||
log_warn "Failed to deploy syntaxis-lib.nu"
|
||
failed=$((failed + 1))
|
||
} && {
|
||
deployed=$((deployed + 1))
|
||
log_info " ✅ Deployed syntaxis-lib.nu"
|
||
}
|
||
fi
|
||
|
||
# Deploy binary-specific wrappers (cli, tui, api)
|
||
for binary in syntaxis-cli syntaxis-tui syntaxis-api; do
|
||
# Deploy binary wrapper script (e.g., syntaxis-cli.nu)
|
||
if [[ -f "$scripts_dir/${binary}.nu" ]]; then
|
||
cp "$scripts_dir/${binary}.nu" "$config_scripts_dir/" || {
|
||
log_warn "Failed to deploy ${binary}.nu"
|
||
failed=$((failed + 1))
|
||
} && {
|
||
deployed=$((deployed + 1))
|
||
chmod 755 "$config_scripts_dir/${binary}.nu"
|
||
log_info " ✅ Deployed ${binary}.nu"
|
||
}
|
||
fi
|
||
|
||
# Deploy binary library script (e.g., syntaxis-cli-lib.nu)
|
||
if [[ -f "$scripts_dir/${binary}-lib.nu" ]]; then
|
||
cp "$scripts_dir/${binary}-lib.nu" "$config_scripts_dir/" || {
|
||
log_warn "Failed to deploy ${binary}-lib.nu"
|
||
failed=$((failed + 1))
|
||
} && {
|
||
deployed=$((deployed + 1))
|
||
chmod 755 "$config_scripts_dir/${binary}-lib.nu"
|
||
log_info " ✅ Deployed ${binary}-lib.nu"
|
||
}
|
||
fi
|
||
done
|
||
|
||
if [[ $deployed -gt 0 ]]; then
|
||
log_success "Wrapper scripts deployed ($deployed files)"
|
||
return 0
|
||
else
|
||
log_warn "No wrapper scripts found in bundle"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
# Deploy configuration files
|
||
deploy_configs() {
|
||
local config_dir="${CONFIG_DIR:-$HOME/.config/syntaxis}"
|
||
|
||
log_info "Deploying configuration files to $config_dir..."
|
||
|
||
if [[ ! -d "$BUNDLE_DIR/configs" ]]; then
|
||
log_warn "No configs directory found in bundle"
|
||
return 0
|
||
fi
|
||
|
||
if ! ensure_directory "$config_dir" "config directory"; then
|
||
return 1
|
||
fi
|
||
|
||
local config_count=0
|
||
|
||
# Copy all config files
|
||
while IFS= read -r -d '' config_file; do
|
||
if [[ -f "$config_file" ]]; then
|
||
local relative="${config_file#$BUNDLE_DIR/configs/}"
|
||
local dest_path="$config_dir/$relative"
|
||
local dest_dir=$(dirname "$dest_path")
|
||
|
||
# Create subdirectories
|
||
mkdir -p "$dest_dir" || return 1
|
||
|
||
# Check if file exists
|
||
if [[ -f "$dest_path" ]] && [[ $FORCE == false ]]; then
|
||
log_info "Config already exists: $relative (keeping original)"
|
||
continue
|
||
fi
|
||
|
||
cp "$config_file" "$dest_path" || {
|
||
log_error "Failed to deploy config: $relative"
|
||
continue
|
||
}
|
||
|
||
log_success "Deployed: $relative"
|
||
config_count=$((config_count + 1))
|
||
fi
|
||
done < <(find "$BUNDLE_DIR/configs" -type f -print0)
|
||
|
||
if [[ $config_count -gt 0 ]]; then
|
||
log_success "Deployed $config_count configuration file(s)"
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# Create installation manifest
|
||
create_manifest() {
|
||
local manifest_dir="$HOME/.syntaxis"
|
||
local manifest_file="$manifest_dir/manifest.toml"
|
||
|
||
mkdir -p "$manifest_dir" || return 1
|
||
|
||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||
local config_dir="${CONFIG_DIR:-$HOME/.config/syntaxis}"
|
||
|
||
cat > "$manifest_file" << EOF
|
||
[installation]
|
||
timestamp = "$timestamp"
|
||
prefix = "$(eval echo ~)/.local"
|
||
version = "0.1.0"
|
||
bundle = "$BUNDLE_NAME"
|
||
|
||
[[binaries]]
|
||
name = "syntaxis-cli"
|
||
installed_at = "$timestamp"
|
||
|
||
[[binaries]]
|
||
name = "syntaxis-tui"
|
||
installed_at = "$timestamp"
|
||
|
||
[[binaries]]
|
||
name = "syntaxis-api"
|
||
installed_at = "$timestamp"
|
||
|
||
[deployment]
|
||
config_dir = "$config_dir"
|
||
deployed_at = "$timestamp"
|
||
EOF
|
||
|
||
log_success "Manifest saved: $manifest_file"
|
||
return 0
|
||
}
|
||
|
||
# Print section header
|
||
print_header() {
|
||
local title="$1"
|
||
echo ""
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE}${title}${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
}
|
||
|
||
# Show installation summary
|
||
show_summary() {
|
||
echo ""
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${GREEN}✅ Installation Complete!${NC}"
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
echo "Installation Details:"
|
||
echo " Prefix: $INSTALL_PREFIX"
|
||
echo " Binaries: $BIN_DIR"
|
||
echo " Configs: ${CONFIG_DIR:-$HOME/.config/syntaxis}"
|
||
echo " Manifest: $HOME/.syntaxis/manifest.toml"
|
||
echo ""
|
||
echo "Quick Start:"
|
||
echo " syntaxis-cli --help # CLI documentation"
|
||
echo " syntaxis-tui # Launch terminal UI"
|
||
echo " syntaxis-api # Start API server (port 3000)"
|
||
echo ""
|
||
echo "Next Steps:"
|
||
echo " 1. Ensure $BIN_DIR is in your PATH"
|
||
echo " 2. Reload your shell: source ~/.bashrc (or ~/.zshrc)"
|
||
echo " 3. Verify: syntaxis-cli --version"
|
||
echo ""
|
||
echo "Configuration:"
|
||
echo " Edit configs in: ${CONFIG_DIR:-$HOME/.config/syntaxis}/"
|
||
echo " Set environment: export SYNTAXIS_CONFIG_DIR=${CONFIG_DIR:-$HOME/.config/syntaxis}"
|
||
echo ""
|
||
}
|
||
|
||
# Main installation flow
|
||
main() {
|
||
parse_args "$@"
|
||
|
||
if [[ $HELP == true ]]; then
|
||
show_help
|
||
exit 0
|
||
fi
|
||
|
||
# Welcome message
|
||
echo -e "${BLUE}"
|
||
echo "╔════════════════════════════════════════════════════════╗"
|
||
echo "║ syntaxis Bundle Installer ║"
|
||
echo "║ Offline Installation Tool ║"
|
||
echo "╚════════════════════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
|
||
log "Starting installation from: $BUNDLE_DIR"
|
||
|
||
# Validate bundle
|
||
if ! validate_bundle; then
|
||
log_error "Bundle validation failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Verify checksums if requested
|
||
if [[ $VERIFY == true ]]; then
|
||
if ! verify_checksums; then
|
||
if [[ $FORCE == false ]]; then
|
||
log_error "Checksum verification failed, aborting installation"
|
||
exit 1
|
||
else
|
||
log_warn "Checksum verification failed, but continuing (--force)"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Detect installation prefix
|
||
detect_install_prefix
|
||
|
||
# Show configuration
|
||
echo ""
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE}Installation Configuration${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo " Installation Prefix: $INSTALL_PREFIX"
|
||
echo " Binaries Directory: $BIN_DIR"
|
||
echo " Config Directory: ${CONFIG_DIR:-$HOME/.config/syntaxis}"
|
||
echo " Backup Existing: $BACKUP"
|
||
echo " Verify Checksums: $VERIFY"
|
||
echo " Force Overwrite: $FORCE"
|
||
echo " Dry Run: $DRY_RUN"
|
||
echo ""
|
||
|
||
if [[ $DRY_RUN == true ]]; then
|
||
log_info "DRY RUN MODE - No changes will be made"
|
||
return 0
|
||
fi
|
||
|
||
# Confirm installation (unless unattended)
|
||
if [[ $UNATTENDED == false ]]; then
|
||
echo -n "Continue with installation? (y/n) "
|
||
read -r response
|
||
if [[ $response != [yY] ]]; then
|
||
log "Installation cancelled"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# Ensure directories exist and are writable
|
||
if ! ensure_directory "$BIN_DIR" "installation directory"; then
|
||
log_error "Cannot write to installation directory"
|
||
exit 1
|
||
fi
|
||
|
||
# Check NuShell availability and inform user
|
||
print_header "Wrapper Configuration"
|
||
if check_nushell; then
|
||
log_success "NuShell detected ($NUSHELL_VERSION)"
|
||
log_info "Wrappers will use full-featured mode with NuShell libraries"
|
||
else
|
||
log_warn "NuShell not detected"
|
||
log_info "Wrappers will use simplified mode (config auto-discovery only)"
|
||
prompt_nushell # This may exit or continue
|
||
fi
|
||
|
||
# Install binaries
|
||
if ! install_binaries; then
|
||
log_error "Binary installation failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Deploy wrapper scripts (for auto-config injection)
|
||
if ! deploy_wrapper_scripts; then
|
||
log_warn "Wrapper scripts deployment failed (continuing - wrappers optional)"
|
||
fi
|
||
|
||
# Deploy configs
|
||
if ! deploy_configs; then
|
||
log_warn "Configuration deployment failed (continuing)"
|
||
fi
|
||
|
||
# Create manifest
|
||
if ! create_manifest; then
|
||
log_warn "Failed to create manifest"
|
||
fi
|
||
|
||
# Show summary
|
||
show_summary
|
||
|
||
log_success "Installation successful"
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|