scripts/build.sh
Rama main · réplica de lectura del repositorio original.
#!/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