syntaxis/install/install.sh

844 lines
23 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
################################################################################
# syntaxis One-Line Installer
#
# Usage:
# curl -sSL https://raw.githubusercontent.com/syntaxis/main/install.sh | bash
# curl -sSL https://raw.githubusercontent.com/syntaxis/main/install.sh | bash -s -- --help
#
# Options:
# --help Show this help message
# --version TAG Install specific version/tag
# --prefix DIR Custom installation directory
# --skip-deps Skip dependency installation
# --no-verify Skip test verification (faster, not recommended)
# --unattended Non-interactive mode
# --keep-build Keep build artifacts
# --config-only Only deploy configurations (skip build)
# --dry-run Show what would be installed
#
# Environment Variables:
# WORKSPACE_INSTALL_DIR Installation directory (default: ~/.local/share/syntaxis)
# WORKSPACE_NO_COLOR Disable colored output
# WORKSPACE_SKIP_TESTS Skip test verification
# CARGO_HOME Cargo installation directory
#
################################################################################
set -euo pipefail
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
INSTALLER_VERSION="1.0.0"
PROJECT_REPO="https://github.com/syntaxis.git"
INSTALL_DIR="${WORKSPACE_INSTALL_DIR:-$HOME/.local/share/syntaxis}"
LOG_FILE="$HOME/.syntaxis/install.log"
MANIFEST_DIR="$HOME/.syntaxis"
CONFIG_DIR="$HOME/.config/syntaxis"
DATA_DIR="$HOME/.local/share/syntaxis"
# Script state
STEP=0
TOTAL_STEPS=8
DRY_RUN=false
SKIP_DEPS=false
NO_VERIFY=false
UNATTENDED=false
KEEP_BUILD=false
CONFIG_ONLY=false
VERSION_TAG="main"
CLEANUP_ON_ERROR=true
# Initialize log file
mkdir -p "$MANIFEST_DIR"
: > "$LOG_FILE"
################################################################################
# Utility Functions
################################################################################
log() {
local msg="$*"
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] $msg" | tee -a "$LOG_FILE"
}
log_error() {
local msg="$*"
echo -e "${RED}[ERROR]${NC} $msg" | tee -a "$LOG_FILE" >&2
}
log_success() {
local msg="$*"
echo -e "${GREEN}${NC} $msg" | tee -a "$LOG_FILE"
}
log_info() {
local msg="$*"
echo -e "${BLUE}${NC} $msg" | tee -a "$LOG_FILE"
}
log_warn() {
local msg="$*"
echo -e "${YELLOW}⚠️${NC} $msg" | tee -a "$LOG_FILE"
}
step() {
STEP=$((STEP + 1))
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}[$STEP/$TOTAL_STEPS]${NC} $*"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
check_command() {
local cmd="$1"
if command -v "$cmd" &> /dev/null; then
return 0
else
return 1
fi
}
prompt_yes_no() {
local question="$1"
local default="${2:-y}"
if [[ "$UNATTENDED" == "true" ]]; then
return 0 # Default to yes in unattended mode
fi
local prompt="$question (y/n) [$default]: "
local response
read -p "$prompt" response
response="${response:-$default}"
if [[ "$response" =~ ^[Yy] ]]; then
return 0
else
return 1
fi
}
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos" ;;
Linux*) echo "linux" ;;
*) 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
}
detect_linux_distro() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
echo "$ID"
else
echo "unknown"
fi
}
show_help() {
cat << 'EOF'
syntaxis One-Line Installer
USAGE:
curl -sSL https://raw.githubusercontent.com/syntaxis/main/install.sh | bash
bash install.sh [OPTIONS]
OPTIONS:
-h, --help Show this help message
-v, --version TAG Install specific version/tag (default: main)
--prefix DIR Custom installation directory
--skip-deps Skip dependency installation (assume already installed)
--no-verify Skip test verification (faster, not recommended)
--unattended Non-interactive mode (use all defaults)
--keep-build Keep build artifacts (default: cleanup)
--config-only Only deploy configurations (skip build)
--dry-run Show what would be installed
ENVIRONMENT VARIABLES:
WORKSPACE_INSTALL_DIR Installation directory
WORKSPACE_NO_COLOR Disable colored output
WORKSPACE_SKIP_TESTS Skip test verification
CARGO_HOME Cargo installation directory
EXAMPLES:
# Standard installation
bash install.sh
# Unattended installation with custom prefix
bash install.sh --unattended --prefix /opt/workspace
# Only deploy configurations (binaries already installed)
bash install.sh --config-only
# See what would be installed without making changes
bash install.sh --dry-run
For more information, visit: https://github.com/syntaxis
EOF
}
rollback_on_error() {
if [[ "$DRY_RUN" == "true" ]] || [[ "$CLEANUP_ON_ERROR" != "true" ]]; then
return
fi
log_warn "Rolling back installation..."
# Remove partially installed binaries
rm -f "$HOME/.cargo/bin/workspace"* 2>/dev/null || true
# Note: Don't remove installation directory as it might be user repo
log_warn "Installation failed. See $LOG_FILE for details."
}
trap rollback_on_error ERR
################################################################################
# Phase 1: Pre-Flight Checks
################################################################################
preflight_checks() {
step "Pre-Flight Checks"
local os=$(detect_os)
local arch=$(detect_arch)
log "Detected OS: $os ($arch)"
if [[ "$os" == "unknown" ]] || [[ "$arch" == "unknown" ]]; then
log_error "Unsupported OS or architecture"
return 1
fi
# Check basic tools
local missing_tools=()
for tool in git curl; do
if ! check_command "$tool"; then
missing_tools+=("$tool")
fi
done
if [[ ${#missing_tools[@]} -gt 0 ]]; then
log_error "Missing required tools: ${missing_tools[*]}"
log "Please install: ${missing_tools[*]}"
if [[ "$os" == "macos" ]]; then
log " brew install ${missing_tools[*]}"
elif [[ "$os" == "linux" ]]; then
local distro=$(detect_linux_distro)
case "$distro" in
ubuntu|debian)
log " sudo apt-get update && sudo apt-get install -y ${missing_tools[*]}"
;;
fedora|rhel)
log " sudo dnf install -y ${missing_tools[*]}"
;;
arch)
log " sudo pacman -S --noconfirm ${missing_tools[*]}"
;;
esac
fi
return 1
fi
log_success "Pre-flight checks passed"
}
################################################################################
# Phase 2: Install Core Dependencies
################################################################################
install_rust() {
if check_command "rustc"; then
local rust_version=$(rustc --version | awk '{print $2}')
log_success "Rust already installed: $rust_version"
return 0
fi
log_info "Installing Rust toolchain..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would install Rust via: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs"
return 0
fi
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.75 2>&1 | tee -a "$LOG_FILE"
# Source cargo environment
if [[ -f "$HOME/.cargo/env" ]]; then
# shellcheck disable=SC1090
source "$HOME/.cargo/env"
fi
if ! check_command "rustc"; then
log_error "Rust installation failed"
return 1
fi
log_success "Rust installed: $(rustc --version)"
}
install_nushell() {
if check_command "nu"; then
local nu_version=$(nu --version 2>&1 | head -1)
log_success "NuShell already installed: $nu_version"
return 0
fi
log_info "Installing NuShell..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would install NuShell via: cargo install nu"
return 0
fi
# Try system package manager first
local os=$(detect_os)
if [[ "$os" == "macos" ]] && check_command "brew"; then
log_info "Installing NuShell via Homebrew..."
brew install nushell 2>&1 | tee -a "$LOG_FILE" || true
fi
# Fallback to cargo
if ! check_command "nu"; then
log_info "Installing NuShell via cargo..."
cargo install nu --locked 2>&1 | tee -a "$LOG_FILE"
fi
if ! check_command "nu"; then
log_error "NuShell installation failed"
return 1
fi
log_success "NuShell installed: $(nu --version 2>&1 | head -1)"
}
install_just() {
if check_command "just"; then
local just_version=$(just --version)
log_success "Just already installed: $just_version"
return 0
fi
log_info "Installing Just task runner..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would install Just via: cargo install just"
return 0
fi
cargo install just 2>&1 | tee -a "$LOG_FILE"
if ! check_command "just"; then
log_error "Just installation failed"
return 1
fi
log_success "Just installed: $(just --version)"
}
install_system_deps() {
local os=$(detect_os)
log_info "Checking system dependencies..."
case "$os" in
macos)
if ! check_command "cc"; then
log_info "Installing Xcode Command Line Tools..."
if [[ "$DRY_RUN" != "true" ]]; then
xcode-select --install 2>&1 | tee -a "$LOG_FILE" || true
# Wait for installation
sudo xcode-select --switch /Library/Developer/CommandLineTools 2>/dev/null || true
fi
fi
log_success "System dependencies ready"
;;
linux)
local distro=$(detect_linux_distro)
local deps=()
# Check for required tools
check_command "gcc" || deps+=("gcc")
check_command "make" || deps+=("make")
check_command "pkg-config" || deps+=("pkg-config")
# Check for sqlite development headers
if ! pkg-config --exists sqlite3 2>/dev/null; then
case "$distro" in
ubuntu|debian) deps+=("libsqlite3-dev") ;;
fedora|rhel) deps+=("sqlite-devel") ;;
arch) deps+=("sqlite") ;;
esac
fi
if [[ ${#deps[@]} -gt 0 ]]; then
log_info "Installing dependencies: ${deps[*]}"
if [[ "$DRY_RUN" != "true" ]]; then
case "$distro" in
ubuntu|debian)
sudo apt-get update && sudo apt-get install -y build-essential pkg-config libsqlite3-dev 2>&1 | tee -a "$LOG_FILE"
;;
fedora|rhel)
sudo dnf install -y gcc gcc-c++ make pkg-config sqlite-devel 2>&1 | tee -a "$LOG_FILE"
;;
arch)
sudo pacman -S --noconfirm base-devel pkg-config sqlite 2>&1 | tee -a "$LOG_FILE"
;;
*)
log_warn "Unknown Linux distribution. Install build essentials manually."
;;
esac
fi
fi
log_success "System dependencies ready"
;;
esac
}
install_dependencies() {
if [[ "$SKIP_DEPS" == "true" ]]; then
log_warn "Skipping dependency installation (--skip-deps)"
return 0
fi
step "Install Core Dependencies"
install_rust
install_nushell
install_just
install_system_deps
log_success "All dependencies installed"
}
################################################################################
# Phase 3: Clone/Verify Repository
################################################################################
clone_or_verify_repo() {
step "Clone/Verify Repository"
# Check if already in a git repository
if git rev-parse --git-dir &> /dev/null; then
log_success "Already in a git repository"
INSTALL_DIR="$(pwd)"
cd "$INSTALL_DIR"
else
log_info "Cloning syntaxis repository..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would clone: $PROJECT_REPO to $INSTALL_DIR"
return 0
fi
if [[ -d "$INSTALL_DIR" ]]; then
if [[ -d "$INSTALL_DIR/.git" ]]; then
log_info "Repository already exists, updating..."
cd "$INSTALL_DIR"
git pull origin "$VERSION_TAG" 2>&1 | tee -a "$LOG_FILE"
else
log_error "Directory exists but is not a git repository: $INSTALL_DIR"
return 1
fi
else
mkdir -p "$(dirname "$INSTALL_DIR")"
git clone -b "$VERSION_TAG" "$PROJECT_REPO" "$INSTALL_DIR" 2>&1 | tee -a "$LOG_FILE"
cd "$INSTALL_DIR"
fi
fi
# Verify repository structure
local required_files=("Cargo.toml" "workspace-manager/Cargo.toml" "Justfile")
for file in "${required_files[@]}"; do
if [[ ! -f "$file" ]]; then
log_error "Missing required file: $file"
return 1
fi
done
log_success "Repository verified: $INSTALL_DIR"
}
################################################################################
# Phase 4: Build Workspace
################################################################################
build_workspace() {
if [[ "$CONFIG_ONLY" == "true" ]]; then
log_warn "Skipping build (--config-only)"
return 0
fi
step "Build Workspace"
log_info "Building workspace binaries (this may take 10-20 minutes)..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would run: cargo build --release --workspace (excluding workspace-api, workspace-vapora)"
return 0
fi
# Build release binaries
RUST_BACKTRACE=1 cargo build --release --workspace \
--exclude workspace-api \
--exclude workspace-vapora \
2>&1 | tee -a "$LOG_FILE"
if [[ $? -ne 0 ]]; then
log_error "Build failed"
return 1
fi
# Run tests
if [[ "$NO_VERIFY" != "true" ]]; then
log_info "Running tests..."
cargo test --release --workspace --lib \
--exclude workspace-api \
--exclude workspace-vapora \
2>&1 | tee -a "$LOG_FILE"
if [[ $? -ne 0 ]]; then
log_warn "Some tests failed, continuing anyway..."
fi
fi
log_success "Build completed"
}
################################################################################
# Phase 5: Install Binaries
################################################################################
install_binaries() {
if [[ "$CONFIG_ONLY" == "true" ]]; then
log_warn "Skipping binary installation (--config-only)"
return 0
fi
step "Install Binaries"
log_info "Installing binaries to ~/.cargo/bin..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would install: workspace, workspace-tui, workspace-dashboard"
return 0
fi
local binaries=("workspace-cli" "workspace-tui" "workspace-dashboard")
for binary in "${binaries[@]}"; do
log_info "Installing $binary..."
if ! cargo install --path "workspace-manager/crates/$binary" 2>&1 | tee -a "$LOG_FILE"; then
log_error "Failed to install $binary"
return 1
fi
done
# Verify installations
for binary in "workspace" "workspace-tui" "workspace-dashboard"; do
if ! check_command "$binary"; then
log_error "Binary not found in PATH: $binary"
return 1
fi
done
log_success "All binaries installed: workspace, workspace-tui, workspace-dashboard"
}
################################################################################
# Phase 6: Deploy Configurations
################################################################################
deploy_configurations() {
step "Deploy Configurations"
log_info "Deploying configuration files..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would deploy configurations to $CONFIG_DIR"
return 0
fi
# Create directories
mkdir -p "$CONFIG_DIR/features"
mkdir -p "$DATA_DIR"
mkdir -p "$MANIFEST_DIR"
# Deploy main config
if [[ -f "workspace-manager/workspace-api-config.template.toml" ]]; then
cp "workspace-manager/workspace-api-config.template.toml" "$CONFIG_DIR/workspace-api.toml"
log_success "Deployed: workspace-api.toml"
else
log_warn "Main config template not found"
fi
# Deploy feature configs
if [[ -d "workspace-manager/configs/features" ]]; then
for template in workspace-manager/configs/features/*.template; do
if [[ -f "$template" ]]; then
local filename=$(basename "$template" .template)
cp "$template" "$CONFIG_DIR/features/$filename"
fi
done
local count=$(find "$CONFIG_DIR/features" -type f | wc -l)
log_success "Deployed: $count feature configurations"
else
log_warn "Feature configs directory not found"
fi
log_success "Configurations deployed to: $CONFIG_DIR"
}
################################################################################
# Phase 7: Setup Environment
################################################################################
setup_environment() {
step "Setup Environment"
log_info "Setting up environment variables and PATH..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would update shell configuration files"
return 0
fi
# Detect shell
local shell_name=$(basename "$SHELL")
local shell_rc=""
case "$shell_name" in
bash)
shell_rc="$HOME/.bashrc"
;;
zsh)
shell_rc="$HOME/.zshrc"
;;
fish)
shell_rc="$HOME/.config/fish/config.fish"
;;
*)
log_warn "Unknown shell: $shell_name"
shell_rc="$HOME/.bashrc"
;;
esac
# Add cargo bin to PATH if not already present
if [[ -n "$shell_rc" ]] && [[ -f "$shell_rc" ]]; then
if ! grep -q '.cargo/bin' "$shell_rc"; then
echo "" >> "$shell_rc"
echo "# syntaxis" >> "$shell_rc"
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> "$shell_rc"
log_success "Updated $shell_rc"
fi
fi
# Export environment variables for current session
export PATH="$HOME/.cargo/bin:$PATH"
export WORKSPACE_CONFIG_DIR="$CONFIG_DIR"
export WORKSPACE_DATA_DIR="$DATA_DIR"
log_success "Environment configured"
}
################################################################################
# Phase 8: Verification & Summary
################################################################################
verify_installation() {
step "Verify Installation"
log_info "Verifying installation..."
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY RUN] Would verify binaries and configurations"
return 0
fi
local verification_ok=true
# Check binaries
for binary in workspace workspace-tui workspace-dashboard; do
if check_command "$binary"; then
log_success "Found: $binary"
else
log_warn "Not found: $binary"
verification_ok=false
fi
done
# Check configurations
if [[ -f "$CONFIG_DIR/workspace-api.toml" ]]; then
log_success "Found: workspace-api.toml"
else
log_warn "Not found: workspace-api.toml"
verification_ok=false
fi
local feature_count=$(find "$CONFIG_DIR/features" -type f 2>/dev/null | wc -l)
if [[ $feature_count -gt 0 ]]; then
log_success "Found: $feature_count feature configurations"
else
log_warn "No feature configurations found"
fi
if [[ "$verification_ok" == "true" ]]; then
log_success "Installation verified successfully"
return 0
else
log_warn "Some components missing, but installation may still be functional"
return 0 # Don't fail on partial verification
fi
}
show_summary() {
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}✨ Installation Complete!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "Installation Details:"
echo " Version: 0.1.0"
echo " Installation: $INSTALL_DIR"
echo " Binaries: workspace, workspace-tui, workspace-dashboard"
echo " Config: $CONFIG_DIR"
echo " Data: $DATA_DIR"
echo " Log: $LOG_FILE"
echo ""
echo "Quick Start:"
echo " workspace --help # CLI documentation"
echo " workspace-tui # Launch TUI"
echo " cd $INSTALL_DIR && just run-dashboard # Start web dashboard (port 3000)"
echo ""
echo "Next Steps:"
echo " 1. Customize config: nano $CONFIG_DIR/workspace-api.toml"
echo " 2. Create a project: workspace project create my-project"
echo " 3. Read docs: https://github.com/syntaxis"
echo ""
# Remind to reload shell
if [[ -n "$SHELL" ]]; then
echo -e "${YELLOW}⚠️${NC} Reload your shell for PATH changes to take effect:"
echo " source ~/$( basename "$SHELL" )rc"
fi
echo ""
log_success "Thank you for installing syntaxis!"
}
################################################################################
# Main Installation Flow
################################################################################
main() {
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-v|--version)
VERSION_TAG="$2"
shift 2
;;
--prefix)
INSTALL_DIR="$2"
shift 2
;;
--skip-deps)
SKIP_DEPS=true
shift
;;
--no-verify)
NO_VERIFY=true
shift
;;
--unattended)
UNATTENDED=true
shift
;;
--keep-build)
KEEP_BUILD=true
shift
;;
--config-only)
CONFIG_ONLY=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Show welcome message
echo -e "${BLUE}"
echo "╔════════════════════════════════════════════════════════╗"
echo "║ syntaxis Installer v$INSTALLER_VERSION"
echo "║ One-Command Installation for Project Management ║"
echo "╚════════════════════════════════════════════════════════╝"
echo -e "${NC}"
log "Starting installation..."
log "Installation directory: $INSTALL_DIR"
log "Log file: $LOG_FILE"
# Execute installation phases
preflight_checks || exit 1
install_dependencies || exit 1
clone_or_verify_repo || exit 1
build_workspace || exit 1
install_binaries || exit 1
deploy_configurations || exit 1
setup_environment || exit 1
verify_installation || exit 1
# Show summary
show_summary
# Cleanup build artifacts if requested
if [[ "$KEEP_BUILD" != "true" ]] && [[ "$DRY_RUN" != "true" ]]; then
log_info "Cleaning up build artifacts..."
cd "$INSTALL_DIR"
rm -rf target 2>/dev/null || true
log_success "Cleanup complete"
fi
log_success "Installation successful"
exit 0
}
# Run main function
main "$@"