#!/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 "$@"