Ctrl K
ComunidadGuías…

scripts/build.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/bash

################################################################################
# ActaLog Build Script for Ubuntu
################################################################################
# This script sets up a fresh Ubuntu system with all dependencies needed to
# build and run ActaLog (CrossFit workout tracker), and can also be used to
# update an existing installation.
#
# What this script does:
# 1. Updates system packages
# 2. Installs/updates Go (backend language)
# 3. Installs/updates Node.js and npm (frontend dependencies)
# 4. Installs SQLite3 (default database)
# 5. Installs development tools (make, git, etc.)
# 6. Installs/updates Go development tools (air, golangci-lint, goimports)
# 7. Updates backend dependencies
# 8. Updates frontend dependencies
# 9. Builds the backend application
# 10. Builds the frontend application
# 11. Creates environment configuration files (if needed)
# 12. Sets up the database (if needed)
#
# Usage:
#   chmod +x build.sh
#   ./build.sh              # Fresh install (interactive mode)
#   ./build.sh --update     # Update mode (updates all packages)
#   ./build.sh --rebuild    # Rebuild only (skip package updates)
#   ./build.sh --help       # Show help
#
# Requirements:
#   - Ubuntu 20.04+ installation
#   - sudo privileges
#   - Internet connection
################################################################################

set -e  # Exit immediately if a command exits with a non-zero status
set -u  # Treat unset variables as an error

################################################################################
# Configuration Variables
################################################################################

SCRIPT_VERSION="1.0.1"  # Build script version (increment on every change)

GO_VERSION="1.25.4"  # Go version to install (adjust as needed)
NODE_VERSION="24"     # Node.js major version (LTS)
MIN_NODE_MAJOR="18"   # Minimum Node.js major version required to run the frontend
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Project root (parent of scripts/)
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WEB_DIR="$PROJECT_ROOT/web"

# Mode flags (controlled by command-line arguments)
UPDATE_MODE=false     # Update all packages to latest versions
REBUILD_MODE=false    # Rebuild only, skip package updates
INTERACTIVE_MODE=true # Ask for confirmation (disabled in update mode)

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

################################################################################
# Helper Functions
################################################################################

# Print colored status messages
print_status() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Check if command exists
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# Ensure sudo is available when running installation/update steps that require
# elevated privileges. Rebuild-only mode does not require sudo.
ensure_sudo_for_installs() {
    if ! command_exists sudo; then
        print_error "This script needs 'sudo' for installation/update steps but 'sudo' was not found in PATH."
        print_error "If you intend to run only a rebuild (no system package installation), run: ./build.sh --rebuild"
        print_error "Otherwise install 'sudo' or run this script as a user that has sudo privileges."
        exit 1
    fi
}

# Compare version numbers (returns 0 if v1 < v2, 1 if v1 >= v2)
version_lt() {
    [ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ] && [ "$1" != "$2" ]
}

# Show help message
show_help() {
    cat << EOF
ActaLog Build Script v${SCRIPT_VERSION} - Build and update ActaLog on Ubuntu

USAGE:
    ./build.sh [OPTIONS]

OPTIONS:
    (no options)        Fresh installation with interactive prompts
    --update            Update mode: updates all packages and rebuilds
                        - Updates Go to version $GO_VERSION
                        - Updates Node.js to version $NODE_VERSION
                        - Updates all Go tools (air, golangci-lint, goimports)
                        - Updates all backend dependencies (go mod tidy)
                        - Updates all frontend dependencies (npm install)
                        - Rebuilds backend and frontend
                        - Non-interactive, preserves .env files and database

    --rebuild           Rebuild mode: only rebuild applications
                        - Updates backend dependencies (go mod download)
                        - Updates frontend dependencies (npm install)
                        - Rebuilds backend and frontend
                        - Skips system package and tool updates
                        - Fast, non-interactive

    --help, -h          Show this help message

EXAMPLES:
    # Fresh installation (first time setup)
    ./build.sh

    # Update everything to latest versions
    ./build.sh --update

    # Quick rebuild after code changes
    ./build.sh --rebuild

NOTES:
    - Update mode preserves your .env files and database
    - Update mode automatically installs latest versions without prompts
    - Rebuild mode is fastest for day-to-day development
    - Fresh install mode asks for confirmation before changes

EOF
    exit 0
}

################################################################################
# Step 1: Update System Packages
################################################################################

update_system() {
    print_status "Updating system packages..."
    sudo apt-get update -qq
    sudo apt-get upgrade -y -qq
    print_success "System packages updated"
}

################################################################################
# Step 2: Install Essential Build Tools
################################################################################

install_build_tools() {
    print_status "Installing essential build tools (make, git, curl, wget)..."
    sudo apt-get install -y -qq \
        build-essential \
        make \
        git \
        curl \
        wget \
        ca-certificates \
        gnupg \
        lsb-release
    print_success "Build tools installed"
}

################################################################################
# Step 3: Install Go
################################################################################

install_go() {
    local should_install=false

    if command_exists go; then
        INSTALLED_GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//')

        if [ "$UPDATE_MODE" = true ]; then
            # In update mode, check if we need to upgrade
            if version_lt "$INSTALLED_GO_VERSION" "$GO_VERSION"; then
                print_status "Upgrading Go from $INSTALLED_GO_VERSION to $GO_VERSION..."
                should_install=true
            elif [ "$INSTALLED_GO_VERSION" != "$GO_VERSION" ]; then
                print_status "Installing Go $GO_VERSION (current: $INSTALLED_GO_VERSION)..."
                should_install=true
            else
                print_success "Go $GO_VERSION is already installed (up to date)"
                return
            fi
        else
            # Interactive mode
            print_warning "Go is already installed (version: $INSTALLED_GO_VERSION)"

            if [ "$INTERACTIVE_MODE" = true ]; then
                read -p "Do you want to reinstall Go $GO_VERSION? (y/n) " -n 1 -r
                echo
                if [[ $REPLY =~ ^[Yy]$ ]]; then
                    should_install=true
                else
                    print_status "Skipping Go installation"
                    return
                fi
            else
                return
            fi
        fi
    else
        print_status "Installing Go $GO_VERSION..."
        should_install=true
    fi

    if [ "$should_install" = true ]; then
        # Download Go binary
        cd /tmp
        wget -q "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"

        # Remove any existing Go installation
        sudo rm -rf /usr/local/go

        # Extract Go to /usr/local
        sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz"

        # Add Go to PATH if not already present
        if ! grep -q "/usr/local/go/bin" ~/.bashrc; then
            echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
            echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.bashrc
        fi

        # Set PATH for current session
        export PATH=$PATH:/usr/local/go/bin
        export PATH=$PATH:$HOME/go/bin

        # Clean up
        rm "go${GO_VERSION}.linux-amd64.tar.gz"

        print_success "Go $GO_VERSION installed successfully"
        go version
    fi
}

################################################################################
# Step 4: Install Node.js and npm
################################################################################

install_nodejs() {
    local should_install=false

    if command_exists node; then
        INSTALLED_NODE_VERSION=$(node --version)
        NODE_MAJOR=$(node --version | cut -d'.' -f1 | sed 's/v//')

        if [ "$UPDATE_MODE" = true ]; then
            # In update mode, check if we need to upgrade
            if [ "$NODE_MAJOR" -ne "$NODE_VERSION" ]; then
                print_status "Upgrading Node.js from v$NODE_MAJOR to v$NODE_VERSION..."
                should_install=true
            else
                print_success "Node.js v$NODE_MAJOR is already installed (up to date)"
                return
            fi
        else
            # Interactive mode
            if [ "$NODE_MAJOR" -eq "$NODE_VERSION" ]; then
                print_success "Correct Node.js version already installed (v$INSTALLED_NODE_VERSION)"
                return
            else
                print_warning "Node.js is already installed (version: $INSTALLED_NODE_VERSION)"
                print_status "Target version is Node.js $NODE_VERSION"
                should_install=true
            fi
        fi
    else
        print_status "Installing Node.js $NODE_VERSION (LTS)..."
        should_install=true
    fi

    if [ "$should_install" = true ]; then
        # Add NodeSource repository
        curl -fsSL "https://deb.nodesource.com/setup_${NODE_VERSION}.x" | sudo -E bash -

        # Install Node.js and npm
        sudo apt-get install -y -qq nodejs

        print_success "Node.js and npm installed successfully"
        node --version
        npm --version
    fi
}

################################################################################
# Step 5: Install SQLite3
################################################################################

install_sqlite() {
    if command_exists sqlite3; then
        print_warning "SQLite3 is already installed"
        sqlite3 --version
        return
    fi

    print_status "Installing SQLite3..."
    sudo apt-get install -y -qq sqlite3 libsqlite3-dev
    print_success "SQLite3 installed successfully"
    sqlite3 --version
}

################################################################################
# Step 6: Install Go Development Tools
################################################################################

install_go_tools() {
    print_status "Installing/updating Go development tools..."

    # Ensure GOPATH is set
    export GOPATH=$HOME/go
    export PATH=$PATH:$GOPATH/bin

    # Install air (hot reload for Go)
    if ! command_exists air; then
        print_status "Installing air (hot reload)..."
        go install github.com/air-verse/air@latest
    elif [ "$UPDATE_MODE" = true ]; then
        print_status "Updating air to latest version..."
        go install github.com/air-verse/air@latest
    else
        print_warning "air is already installed"
    fi

    # Install golangci-lint (linter)
    if ! command_exists golangci-lint; then
        print_status "Installing golangci-lint (linter)..."
        curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
    elif [ "$UPDATE_MODE" = true ]; then
        print_status "Updating golangci-lint to latest version..."
        curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
    else
        print_warning "golangci-lint is already installed"
    fi

    # Install goimports (import formatter)
    if ! command_exists goimports; then
        print_status "Installing goimports (import formatter)..."
        go install golang.org/x/tools/cmd/goimports@latest
    elif [ "$UPDATE_MODE" = true ]; then
        print_status "Updating goimports to latest version..."
        go install golang.org/x/tools/cmd/goimports@latest
    else
        print_warning "goimports is already installed"
    fi

    print_success "Go development tools installed/updated"
}

################################################################################
# Step 7: Install Backend Dependencies
################################################################################

install_backend_deps() {
    print_status "Installing backend Go dependencies..."
    cd "$PROJECT_ROOT"

    # Download and tidy Go modules
    go mod download
    go mod tidy

    print_success "Backend dependencies installed"
}

################################################################################
# Step 8: Install Frontend Dependencies
################################################################################

install_frontend_deps() {
    print_status "Installing/updating frontend npm dependencies..."
    cd "$WEB_DIR"

    if [ "$UPDATE_MODE" = true ]; then
        # In update mode, update all dependencies to latest compatible versions
        print_status "Updating npm packages to latest versions..."
        npm update

        # Optionally, audit and fix vulnerabilities
        print_status "Auditing for security vulnerabilities..."
        npm audit fix || true  # Don't fail if audit fix doesn't work
    else
        # Clean install (removes node_modules and package-lock.json if they exist)
        if [ -d "node_modules" ]; then
            print_warning "Removing existing node_modules..."
            rm -rf node_modules
        fi

        if [ -f "package-lock.json" ]; then
            print_warning "Removing existing package-lock.json..."
            rm -f package-lock.json
        fi
    fi

    # Install dependencies
    npm install

    print_success "Frontend dependencies installed/updated"
    cd "$SCRIPT_DIR"
}

################################################################################
# Step 9: Create Environment Configuration Files
################################################################################

setup_env_files() {
    print_status "Setting up environment configuration files..."

    # Backend .env file
    if [ ! -f "$PROJECT_ROOT/.env" ]; then
        if [ -f "$PROJECT_ROOT/.env.example" ]; then
            print_status "Creating .env from .env.example..."
            cp "$PROJECT_ROOT/.env.example" "$PROJECT_ROOT/.env"

            # Generate a random JWT secret
            JWT_SECRET=$(openssl rand -base64 32)

            # Update JWT_SECRET in .env
            if command_exists sed; then
                # macOS and Linux compatible sed
                # Use | as delimiter to avoid conflicts with / in base64 strings
                if [[ "$OSTYPE" == "darwin"* ]]; then
                    sed -i '' "s|JWT_SECRET=.*|JWT_SECRET=$JWT_SECRET|" "$PROJECT_ROOT/.env"
                else
                    sed -i "s|JWT_SECRET=.*|JWT_SECRET=$JWT_SECRET|" "$PROJECT_ROOT/.env"
                fi
                print_success "Generated random JWT_SECRET"
            else
                print_warning "Could not auto-generate JWT_SECRET, please update .env manually"
            fi

            print_success "Backend .env file created"
        else
            print_warning ".env.example not found, skipping .env creation"
        fi
    else
        print_warning "Backend .env file already exists, skipping..."
    fi

    # Frontend .env file (optional for production)
    if [ ! -f "$WEB_DIR/.env" ]; then
        if [ -f "$WEB_DIR/.env.example" ]; then
            print_status "Creating web/.env from .env.example..."
            cp "$WEB_DIR/.env.example" "$WEB_DIR/.env"
            print_success "Frontend .env file created"
        fi
    else
        print_warning "Frontend .env file already exists, skipping..."
    fi
}

################################################################################
# Step 10: Build Backend Application
################################################################################

build_backend() {
    print_status "Building backend application..."
    cd "$PROJECT_ROOT"

    # Run the build using Makefile (run in project root)
    make -C "$PROJECT_ROOT" build

    print_success "Backend built successfully (binary: $PROJECT_ROOT/bin/actalog)"
}

################################################################################
# Step 11: Build Frontend Application
################################################################################

build_frontend() {
    print_status "Building frontend application..."
    cd "$WEB_DIR"

    # Build for production
    npm run build

    print_success "Frontend built successfully (output: $WEB_DIR/dist)"
    cd "$PROJECT_ROOT"
}

################################################################################
# Step 12: Initialize Database
################################################################################

init_database() {
    print_status "Initializing database..."
    cd "$PROJECT_ROOT"

    # Check if database already exists
    if [ -f "actalog.db" ]; then
        print_warning "Database file (actalog.db) already exists"
        read -p "Do you want to remove it and create a fresh database? (y/n) " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            rm actalog.db
            print_status "Removed existing database"
        else
            print_status "Keeping existing database"
            return
        fi
    fi

    # Database will be created automatically when the app runs
    # Migrations will run on first startup
    print_success "Database will be initialized on first run"
}

################################################################################
# Step 13: Create Run Script
################################################################################

create_run_script() {
    print_status "Creating convenience run script..."

    cat > "$SCRIPT_DIR/run.sh" << 'EOF'
#!/bin/bash
# Convenience script to run ActaLog in production mode

# Navigate to project directory
cd "$(dirname "$0")"

# Export Go paths
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin

# Run the application
echo "Starting ActaLog..."
echo "Backend will run on http://localhost:8080"
echo "Press Ctrl+C to stop"
echo ""

./bin/actalog
EOF

    chmod +x "$SCRIPT_DIR/run.sh"
    print_success "Created run.sh script"
}

################################################################################
# Step 14: Print Summary
################################################################################

print_summary() {
    echo ""
    echo "================================================================================"
    if [ "$UPDATE_MODE" = true ]; then
        print_success "ActaLog update completed successfully!"
    elif [ "$REBUILD_MODE" = true ]; then
        print_success "ActaLog rebuild completed successfully!"
    else
        print_success "ActaLog build completed successfully!"
    fi
    echo "================================================================================"
    echo ""
    echo "Next steps:"
    echo ""

    # Only show configuration step for fresh installs
    if [ "$INTERACTIVE_MODE" = true ]; then
        echo "1. Review configuration:"
        echo -e "   ${BLUE}nano .env${NC} (edit JWT_SECRET, DB settings, etc.)"
        echo ""
    fi

    echo "2. Start the backend:"
    echo -e "   ${BLUE}./run.sh${NC}          (production mode)"
    echo -e "   ${BLUE}make run${NC}          (via Makefile)"
    echo -e "   ${BLUE}make dev${NC}          (development mode with hot reload)"
    echo ""
    echo "3. For development with frontend:"
    echo -e "   Terminal 1: ${BLUE}make dev${NC}              (backend with hot reload)"
    echo -e "   Terminal 2: ${BLUE}cd web && npm run dev${NC} (frontend dev server)"
    echo ""
    echo "4. Access the application:"
    echo -e "   Backend API:  ${GREEN}http://localhost:8080${NC}"
    echo -e "   Frontend Dev: ${GREEN}http://localhost:3000${NC} (if running npm run dev)"
    echo -e "   Health Check: ${GREEN}http://localhost:8080/health${NC}"
    echo ""

    # Only show first user info for fresh installs
    if [ "$INTERACTIVE_MODE" = true ]; then
        echo "5. First user registration:"
        echo -e "   The first user to register will automatically become an admin"
        echo ""
    fi

    echo "6. Run tests:"
    echo -e "   ${BLUE}make test${NC}         (all tests with coverage)"
    echo -e "   ${BLUE}make test-unit${NC}    (unit tests only)"
    echo ""
    echo "7. Other useful commands:"
    echo -e "   ${BLUE}make lint${NC}         (run linter)"
    echo -e "   ${BLUE}make fmt${NC}          (format code)"
    echo -e "   ${BLUE}make clean${NC}        (clean build artifacts)"
    echo ""

    # Show update/rebuild options
    if [ "$INTERACTIVE_MODE" = true ]; then
        echo "8. Future updates:"
        echo -e "   ${BLUE}./build.sh --update${NC}   (update all packages and rebuild)"
        echo -e "   ${BLUE}./build.sh --rebuild${NC}  (quick rebuild without updates)"
        echo ""
    fi

    echo "Documentation:"
    echo "   - Architecture:  docs/ARCHITECTURE.md"
    echo "   - Database:      docs/DATABASE_SCHEMA.md"
    echo "   - Development:   CLAUDE.md"
    echo ""
    echo "================================================================================"
}

################################################################################
# Parse Command-Line Arguments
################################################################################

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --update)
                UPDATE_MODE=true
                INTERACTIVE_MODE=false
                REBUILD_MODE=false
                shift
                ;;
            --rebuild)
                REBUILD_MODE=true
                INTERACTIVE_MODE=false
                UPDATE_MODE=false
                shift
                ;;
            --help|-h)
                show_help
                ;;
            *)
                print_error "Unknown option: $1"
                echo "Run './build.sh --help' for usage information"
                exit 1
                ;;
        esac
    done
}

################################################################################
# Main Execution
################################################################################

main() {
    # Show appropriate header based on mode
    echo "================================================================================"
    if [ "$UPDATE_MODE" = true ]; then
        echo "                    ActaLog Update Script v${SCRIPT_VERSION}"
        echo "================================================================================"
        echo ""
        echo "This will update your ActaLog installation:"
        echo "  1. Update system packages"
        echo "  2. Update Go to version $GO_VERSION"
        echo "  3. Update Node.js to version $NODE_VERSION"
        echo "  4. Update Go development tools"
        echo "  5. Update backend dependencies"
        echo "  6. Update frontend dependencies"
        echo "  7. Rebuild backend application"
        echo "  8. Rebuild frontend application"
        echo ""
        echo "NOTE: Your .env files and database will be preserved"
    elif [ "$REBUILD_MODE" = true ]; then
        echo "                    ActaLog Rebuild Script v${SCRIPT_VERSION}"
        echo "================================================================================"
        echo ""
        echo "This will rebuild your ActaLog installation:"
        echo "  1. Update backend dependencies"
        echo "  2. Update frontend dependencies"
        echo "  3. Rebuild backend application"
        echo "  4. Rebuild frontend application"
        echo ""
        echo "NOTE: System packages and tools will not be updated"
    else
        echo "                    ActaLog Build Script for Ubuntu v${SCRIPT_VERSION}"
        echo "================================================================================"
        echo ""
        echo "This script will:"
        echo "  1. Update system packages"
        echo "  2. Install Go $GO_VERSION"
        echo "  3. Install Node.js $NODE_VERSION (LTS)"
        echo "  4. Install SQLite3"
        echo "  5. Install development tools"
        echo "  6. Install Go tools (air, golangci-lint, goimports)"
        echo "  7. Install backend dependencies"
        echo "  8. Install frontend dependencies"
        echo "  9. Create environment files"
        echo " 10. Build backend application"
        echo " 11. Build frontend application"
        echo " 12. Initialize database"
    fi
    echo ""
    echo "================================================================================"
    echo ""

    # Confirm before proceeding (only in interactive mode)
    if [ "$INTERACTIVE_MODE" = true ]; then
        read -p "Continue with installation? (y/n) " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            print_warning "Installation cancelled by user"
            exit 0
        fi
        echo ""
    fi

    # Execute steps based on mode
    if [ "$REBUILD_MODE" = true ]; then
        # Rebuild mode: Skip system packages and tool installation
        print_status "Running in REBUILD mode (fast rebuild without system updates)"
        install_backend_deps
        install_frontend_deps
        build_backend
        build_frontend
    elif [ "$UPDATE_MODE" = true ]; then
        # Ensure sudo is available for update mode
        ensure_sudo_for_installs
        # Update mode: Update everything
        print_status "Running in UPDATE mode (updating all packages to latest versions)"
        update_system
        install_build_tools
        install_go
        install_nodejs
        install_sqlite
        install_go_tools
        install_backend_deps
        install_frontend_deps
        build_backend
        build_frontend
        create_run_script
    else
        # For a fresh install we also need sudo for system package installs
        ensure_sudo_for_installs
        # Fresh install mode: Install everything
        print_status "Running in INSTALL mode (fresh installation)"
        update_system
        install_build_tools
        install_go
        install_nodejs
        install_sqlite
        install_go_tools
        install_backend_deps
        install_frontend_deps
        setup_env_files
        build_backend
        build_frontend
        init_database
        create_run_script
    fi

    # Print summary
    print_summary
}

################################################################################
# Script Entry Point
################################################################################

# Parse command-line arguments
parse_args "$@"

# Run main function
main