#!/bin/bash # MYP Frontend Installation Script for Debian # This script installs and configures the MYP frontend reservation platform # We'll handle errors ourselves rather than exiting immediately # set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" LOG_FILE="$SCRIPT_DIR/frontend-install.log" # Function for logging with timestamps and colors log() { local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local message="[$timestamp] $1" echo -e "$message" | tee -a "$LOG_FILE" } # Function for logging errors with timestamps and colors log_error() { local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local message="[$timestamp] ERROR: $1" echo -e "\033[0;31m$message\033[0m" | tee -a "$LOG_FILE" } # Function for logging warnings with timestamps and colors log_warning() { local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local message="[$timestamp] WARNING: $1" echo -e "\033[0;33m$message\033[0m" | tee -a "$LOG_FILE" } # Function for logging success with timestamps and colors log_success() { local timestamp=$(date +"%Y-%m-%d %H:%M:%S") local message="[$timestamp] SUCCESS: $1" echo -e "\033[0;32m$message\033[0m" | tee -a "$LOG_FILE" } # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Clear log file > "$LOG_FILE" # Add script usage info usage() { echo "Usage: $0 [OPTIONS]" echo "" echo "OPTIONS:" echo " --auto-production Non-interactive installation in production mode" echo " --auto-development Non-interactive installation in development mode" echo " --auto-test Non-interactive installation in test environment mode" echo " --no-sudo Run without sudo privileges (limited functionality)" echo " --help Show this help message" echo "" echo "Example:" echo " $0 --auto-production" exit 1 } # Check for help flag if [ "$1" == "--help" ]; then usage fi # Check if we're running with no-sudo option if [[ "$*" == *"--no-sudo"* ]]; then NO_SUDO=true log "Running in non-sudo mode with limited functionality" else NO_SUDO=false fi log "===== Starting MYP Frontend Installation =====" log "Installation directory: $SCRIPT_DIR" # Display system information log "System information:" uname -a >> "$LOG_FILE" 2>&1 lsb_release -a >> "$LOG_FILE" 2>&1 || cat /etc/os-release >> "$LOG_FILE" 2>&1 log "CPU: $(grep "model name" /proc/cpuinfo | head -n1 | cut -d':' -f2 | sed 's/^ *//')" log "Memory: $(free -h | grep "Mem:" | awk '{print $2}')" log "Disk space: $(df -h / | awk 'NR==2 {print $4}') free on /" # Check for root privileges unless --no-sudo was specified if [ "$EUID" -ne 0 ] && [ "$NO_SUDO" = "false" ]; then log_error "This script must be run as root" log_warning "Use --no-sudo option for limited functionality installation" log_warning "Example: $0 --auto-development --no-sudo" exit 1 fi # System update (only if running as root) if [ "$NO_SUDO" = "false" ]; then log "Updating system packages..." apt update -y >> "$LOG_FILE" 2>&1 || log_error "Failed to update apt repositories" apt upgrade -y >> "$LOG_FILE" 2>&1 || log_warning "Failed to upgrade packages, continuing anyway" # Install required packages log "Installing essential system packages and network tools..." apt install -y curl git wget htop net-tools iptables iputils-ping traceroute nmap tcpdump nftables \ netcat-openbsd dnsutils whois vim nano rsync zip unzip xz-utils sqlite3 \ apt-transport-https ca-certificates gnupg lsb-release bash-completion \ make build-essential libssl-dev zlib1g-dev >> "$LOG_FILE" 2>&1 || log_warning "Some packages failed to install" else log "Skipping system package installation (non-sudo mode)..." # Check if critical tools are available for cmd in curl git wget node npm; do if ! command_exists "$cmd"; then log_warning "$cmd is not installed. This might cause problems." log_warning "Install with: sudo apt install $cmd" fi done fi # Install Docker using the official Docker repository (skip in non-sudo mode) if [ "$NO_SUDO" = "false" ]; then log "Installing Docker from official repository..." if ! command_exists docker; then # Remove any old Docker versions apt remove -y docker docker-engine docker.io containerd runc >> "$LOG_FILE" 2>&1 || true # Add Docker's official GPG key curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg >> "$LOG_FILE" 2>&1 # Set up the stable repository echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null # Update apt and install Docker apt update -y >> "$LOG_FILE" 2>&1 apt install -y docker-ce docker-ce-cli containerd.io >> "$LOG_FILE" 2>&1 # Install Docker Compose log "Installing Docker Compose..." curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose >> "$LOG_FILE" 2>&1 chmod +x /usr/local/bin/docker-compose >> "$LOG_FILE" 2>&1 ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose >> "$LOG_FILE" 2>&1 else log "Docker already installed: $(docker --version)" log "Docker Compose already installed: $(docker-compose --version)" fi else log "Skipping Docker installation (non-sudo mode)..." # Check if Docker is available if command_exists docker; then log "Docker already installed: $(docker --version)" if command_exists docker-compose; then log "Docker Compose already installed: $(docker-compose --version)" else log_warning "Docker Compose not installed. Some features may not work." fi else log_warning "Docker not installed. Container features will not be available." log_warning "Install Docker with: curl -fsSL https://get.docker.com | sudo sh" fi fi # Install Node.js 20.x if ! command_exists node || [ "$(node -v 2>/dev/null | cut -d. -f1 | tr -d 'v')" -lt 20 2>/dev/null ]; then log "Installing Node.js 20.x..." if [ "$NO_SUDO" = "false" ]; then # System-wide installation curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >> "$LOG_FILE" 2>&1 apt install -y nodejs >> "$LOG_FILE" 2>&1 else # User-level installation using nvm (Node Version Manager) log "Installing Node.js using NVM (user-level)..." # Install NVM if not already installed if [ ! -d "$HOME/.nvm" ]; then curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash >> "$LOG_FILE" 2>&1 export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm else export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm fi # Install Node.js 20 using NVM nvm install 20 >> "$LOG_FILE" 2>&1 nvm use 20 >> "$LOG_FILE" 2>&1 fi # Verify installation if command_exists node; then log_success "Node.js $(node -v) installed" else log_error "Node.js installation failed" exit 1 fi else log "Node.js $(node -v) already installed" fi # Install pnpm - with more robust installation approaches if ! command_exists pnpm; then log "Installing pnpm package manager..." # First, try npm installation if command_exists npm; then npm install -g pnpm >> "$LOG_FILE" 2>&1 fi # Check if npm method succeeded if ! command_exists pnpm; then log "Trying alternative pnpm installation method..." curl -fsSL https://get.pnpm.io/install.sh | bash - >> "$LOG_FILE" 2>&1 # Update PATH to include pnpm export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}" export PATH="$PNPM_HOME:$PATH" # Add to ~/.bashrc for future use if it's not already there if ! grep -q "PNPM_HOME" "$HOME/.bashrc" 2>/dev/null; then echo 'export PNPM_HOME="$HOME/.local/share/pnpm"' >> "$HOME/.bashrc" echo 'export PATH="$PNPM_HOME:$PATH"' >> "$HOME/.bashrc" log "Added pnpm to PATH in .bashrc for future terminal sessions" fi # Try standalone installation if previous methods failed if ! command_exists pnpm; then log "Trying standalone installation of pnpm..." # Create bin directory if it doesn't exist mkdir -p "$HOME/.local/bin" # Download standalone PNPM curl -fsSL https://github.com/pnpm/pnpm/releases/download/v9.12.1/pnpm-linuxstatic-x64 -o "$HOME/.local/bin/pnpm" >> "$LOG_FILE" 2>&1 chmod +x "$HOME/.local/bin/pnpm" # Add to PATH export PATH="$HOME/.local/bin:$PATH" # Add to ~/.bashrc if not already there if ! grep -q "/.local/bin" "$HOME/.bashrc" 2>/dev/null; then echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" fi fi fi # Verify installation if command_exists pnpm; then log_success "pnpm $(pnpm --version) installed" else log_error "pnpm installation failed" log_error "Please try to install manually with: npm install -g pnpm" exit 1 fi else log "pnpm $(pnpm --version) already installed" fi # Make sure pnpm is in PATH for this script session export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}" export PATH="$PNPM_HOME:$HOME/.local/bin:$PATH" # Create a .pnpmrc file to ensure proper configuration echo "standalone=true" > "$HOME/.pnpmrc" 2>/dev/null || log_warning "Could not create .pnpmrc file" # Enable and start Docker (skip in non-sudo mode) if [ "$NO_SUDO" = "false" ]; then log "Ensuring Docker is running..." systemctl enable docker >> "$LOG_FILE" 2>&1 || log_warning "Failed to enable Docker service" systemctl start docker >> "$LOG_FILE" 2>&1 || log_warning "Failed to start Docker service" # Configure Docker for multi-architecture builds log "Setting up Docker for multi-architecture builds..." if command_exists docker; then if ! docker buildx ls 2>/dev/null | grep -q "default"; then docker buildx create --name mybuilder --use >> "$LOG_FILE" 2>&1 || log_warning "Failed to configure Docker buildx" log "Docker buildx configured" else log "Docker buildx already configured" fi else log_warning "Docker not available, skipping buildx configuration" fi else log "Skipping Docker service configuration (non-sudo mode)..." fi # Make sure we have the docker directory for configuration mkdir -p "$SCRIPT_DIR/docker/caddy" # Check if Caddyfile exists if [ ! -f "$SCRIPT_DIR/docker/caddy/Caddyfile" ]; then log "Creating Caddyfile template..." cat > "$SCRIPT_DIR/docker/caddy/Caddyfile" << EOF # Caddyfile for MYP Frontend # Replace example.com with your actual domain :80 { # Automatic HTTPS will be enabled if you use a domain name # For local development or internal network, HTTP is fine # Reverse proxy to frontend app reverse_proxy frontend:3000 # Basic headers for security header { # Enable HSTS Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enable XSS protection X-XSS-Protection "1; mode=block" # Prevent content type sniffing X-Content-Type-Options "nosniff" # Clickjacking protection X-Frame-Options "SAMEORIGIN" # Allow backend API access (CORS) Access-Control-Allow-Origin "http://192.168.0.105:5000" Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" Access-Control-Allow-Credentials "true" } # Log access log { output file /var/log/caddy/access.log } } EOF log "Caddyfile created. Edit it to configure your domain if needed." fi # Create GitHub OAuth credentials file if [ ! -d "/srv/myp-env" ]; then log "Creating directory for environment variables..." mkdir -p /srv/myp-env fi if [ ! -f "/srv/myp-env/github.env" ]; then log "Creating GitHub OAuth credentials file with static backend URL..." cat > /srv/myp-env/github.env << EOF # GitHub OAuth Credentials # Replace these with your actual GitHub OAuth app credentials AUTH_GITHUB_ID=your_github_client_id AUTH_GITHUB_SECRET=your_github_client_secret AUTH_SECRET=$(openssl rand -hex 32) AUTH_TRUST_HOST=true NEXT_PUBLIC_BACKEND_URL=http://192.168.0.105:5000 # Add additional environment variables as needed EOF log "ATTENTION: GitHub OAuth credentials file created with generated AUTH_SECRET" log " Backend URL set to http://192.168.0.105:5000" log " Please edit /srv/myp-env/github.env with your actual GitHub OAuth app credentials" fi # Create a copy of the environment file in the git repository log "Creating a copy of the environment file for version control..." if [ ! -f "$SCRIPT_DIR/.env.example" ]; then cat > "$SCRIPT_DIR/.env.example" << EOF # GitHub OAuth Credentials Example # This is a template for the required environment variables AUTH_GITHUB_ID=your_github_client_id AUTH_GITHUB_SECRET=your_github_client_secret AUTH_SECRET=random_string_generated_during_installation AUTH_TRUST_HOST=true NEXT_PUBLIC_BACKEND_URL=http://192.168.0.105:5000 # Add additional environment variables as needed EOF log "Environment example file created at $SCRIPT_DIR/.env.example" fi # Create database directory if it doesn't exist log "Setting up database directory..." mkdir -p /srv/MYP-DB # Determine if we should run in non-interactive mode if [ -n "$1" ] && [ "$1" == "--auto-production" ]; then log "Running in automatic production mode (non-interactive)..." production_mode="y" test_environment="n" elif [ -n "$1" ] && [ "$1" == "--auto-development" ]; then log "Running in automatic development mode (non-interactive)..." production_mode="n" test_environment="n" elif [ -n "$1" ] && [ "$1" == "--auto-test" ]; then log "Running in automatic test environment mode (non-interactive)..." production_mode="n" test_environment="y" else # Interactive mode log "Running in interactive mode..." # Check if we need to run in development or production mode echo "Choose installation type:" echo "1) Production mode (Docker container)" echo "2) Development mode (local dev server)" echo "3) Test environment (integrates with real backend)" read -p "Enter your choice (1-3): " install_choice case $install_choice in 1) production_mode="y" test_environment="n" ;; 2) production_mode="n" test_environment="n" ;; 3) production_mode="n" test_environment="y" ;; *) log "Invalid choice. Defaulting to development mode." production_mode="n" test_environment="n" ;; esac fi if [ "$test_environment" = "y" ] || [ "$test_environment" = "Y" ]; then # Test environment mode - using Docker for integration testing log "Setting up test environment for integration testing..." # Ensure test directory exists mkdir -p "$SCRIPT_DIR/docker" # Create test environment compose file if it doesn't exist if [ ! -f "$SCRIPT_DIR/docker/test-env.yml" ]; then log "Creating test environment docker-compose file..." cat > "$SCRIPT_DIR/docker/test-env.yml" << EOF version: '3.8' services: # Backend for testing backend: build: context: ../../backend dockerfile: Dockerfile container_name: myp-backend-test ports: - "5000:5000" environment: - SECRET_KEY=testsecretkey123456789 - DATABASE_URL=sqlite:///myp.db - FLASK_ENV=development - TESTING=true volumes: - backend-test-data:/app/instance networks: - test-network # Frontend for testing - bound to loopback address frontend-test: build: context: .. dockerfile: Dockerfile container_name: myp-frontend-test environment: - NODE_ENV=development - AUTH_TRUST_HOST=true - AUTH_SECRET=test-secret-key-for-testing-only-do-not-use-in-production - NEXT_PUBLIC_BACKEND_URL=http://backend:5000 ports: - "127.0.0.1:3000:3000" depends_on: - backend networks: - test-network networks: test-network: driver: bridge volumes: backend-test-data: EOF log "Test environment docker-compose file created" fi # Create test integration script if it doesn't exist if [ ! -f "$SCRIPT_DIR/docker/test-integration.sh" ]; then log "Creating test integration script..." cat > "$SCRIPT_DIR/docker/test-integration.sh" << 'EOF' #!/bin/bash # Test integration script - validates if the frontend and backend work together # Specifically designed to test if the production environment setup will work # Get the directory containing this script SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PARENT_DIR="$(dirname "$SCRIPT_DIR")" TEST_LOG="$SCRIPT_DIR/test-integration.log" # Colors for output GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' NC='\033[0m' # No Color # Create or clear log file > "$TEST_LOG" echo -e "${YELLOW}Starting MYP integration test environment...${NC}" | tee -a "$TEST_LOG" echo "Test directory: $SCRIPT_DIR" | tee -a "$TEST_LOG" echo "Parent directory: $PARENT_DIR" | tee -a "$TEST_LOG" echo "Log file: $TEST_LOG" | tee -a "$TEST_LOG" # Check if Docker is available if ! command -v docker &> /dev/null; then echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" | tee -a "$TEST_LOG" exit 1 fi # Function to run docker compose (handles both docker-compose and docker compose syntax) run_docker_compose() { if command -v docker-compose &> /dev/null; then docker-compose "$@" else docker compose "$@" fi } # Stop any existing test environment (cleanup) echo -e "${YELLOW}Cleaning up any existing test environment...${NC}" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" down >> "$TEST_LOG" 2>&1 # Start the test environment echo -e "${YELLOW}Starting test environment with docker-compose...${NC}" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" up -d >> "$TEST_LOG" 2>&1 if [ $? -ne 0 ]; then echo -e "${RED}Failed to start test environment.${NC}" | tee -a "$TEST_LOG" echo "Check the log file for details: $TEST_LOG" | tee -a "$TEST_LOG" exit 1 fi # Wait for backend to be ready echo -e "${YELLOW}Waiting for backend to be ready...${NC}" | tee -a "$TEST_LOG" max_attempts=30 attempt=1 backend_ready=false while [ $attempt -le $max_attempts ] && [ "$backend_ready" = "false" ]; do echo "Checking backend readiness (attempt $attempt/$max_attempts)..." | tee -a "$TEST_LOG" if curl -s http://localhost:5000/api/health &> /dev/null; then backend_ready=true echo -e "${GREEN}Backend is ready!${NC}" | tee -a "$TEST_LOG" else echo "Backend not ready yet, waiting..." | tee -a "$TEST_LOG" sleep 2 attempt=$((attempt+1)) fi done if [ "$backend_ready" = "false" ]; then echo -e "${RED}Backend failed to start properly after $max_attempts attempts${NC}" | tee -a "$TEST_LOG" echo "Logs from backend container:" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" logs backend >> "$TEST_LOG" 2>&1 # Cleanup run_docker_compose -f "$SCRIPT_DIR/test-env.yml" down >> "$TEST_LOG" 2>&1 exit 1 fi # Wait for frontend to be ready echo -e "${YELLOW}Waiting for frontend to be ready...${NC}" | tee -a "$TEST_LOG" max_attempts=30 attempt=1 frontend_ready=false while [ $attempt -le $max_attempts ] && [ "$frontend_ready" = "false" ]; do echo "Checking frontend readiness (attempt $attempt/$max_attempts)..." | tee -a "$TEST_LOG" if curl -s http://127.0.0.1:3000 &> /dev/null; then frontend_ready=true echo -e "${GREEN}Frontend is ready!${NC}" | tee -a "$TEST_LOG" else echo "Frontend not ready yet, waiting..." | tee -a "$TEST_LOG" sleep 2 attempt=$((attempt+1)) fi done if [ "$frontend_ready" = "false" ]; then echo -e "${RED}Frontend failed to start properly after $max_attempts attempts${NC}" | tee -a "$TEST_LOG" echo "Logs from frontend container:" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" logs frontend-test >> "$TEST_LOG" 2>&1 # Cleanup run_docker_compose -f "$SCRIPT_DIR/test-env.yml" down >> "$TEST_LOG" 2>&1 exit 1 fi # Perform basic integration tests echo -e "${YELLOW}Performing basic integration tests...${NC}" | tee -a "$TEST_LOG" # Test 1: Frontend can fetch API data (printers endpoint) echo "Test 1: Frontend can fetch data from backend API..." | tee -a "$TEST_LOG" frontend_container_id=$(docker ps -qf "name=myp-frontend-test") if [ -z "$frontend_container_id" ]; then echo -e "${RED}Failed to find frontend container${NC}" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" down >> "$TEST_LOG" 2>&1 exit 1 fi # Run a simple test inside the frontend container to check API connectivity api_test_result=$(docker exec $frontend_container_id curl -s http://backend:5000/api/health) if [[ "$api_test_result" == *"healthy"* ]]; then echo -e "${GREEN}Test 1 PASSED: Frontend can connect to backend API${NC}" | tee -a "$TEST_LOG" else echo -e "${RED}Test 1 FAILED: Frontend cannot connect to backend API${NC}" | tee -a "$TEST_LOG" echo "API response: $api_test_result" | tee -a "$TEST_LOG" # Don't exit, continue with other tests fi # Test 2: Frontend serves HTML content echo "Test 2: Frontend serves valid HTML content..." | tee -a "$TEST_LOG" frontend_html=$(curl -s http://127.0.0.1:3000) if [[ "$frontend_html" == *""* ]]; then echo -e "${GREEN}Test 2 PASSED: Frontend serves valid HTML${NC}" | tee -a "$TEST_LOG" else echo -e "${RED}Test 2 FAILED: Frontend does not serve valid HTML${NC}" | tee -a "$TEST_LOG" # Don't exit, continue with other tests fi # All tests completed echo -e "${GREEN}Integration tests completed${NC}" | tee -a "$TEST_LOG" # Ask if the environment should be kept running or shutdown echo -e "${YELLOW}Test environment is running at:${NC}" | tee -a "$TEST_LOG" echo "Frontend: http://127.0.0.1:3000" | tee -a "$TEST_LOG" echo "Backend: http://localhost:5000" | tee -a "$TEST_LOG" echo "" | tee -a "$TEST_LOG" read -p "Do you want to keep the test environment running? (y/n): " keep_running if [[ "$keep_running" != "y" && "$keep_running" != "Y" ]]; then echo -e "${YELLOW}Shutting down test environment...${NC}" | tee -a "$TEST_LOG" run_docker_compose -f "$SCRIPT_DIR/test-env.yml" down >> "$TEST_LOG" 2>&1 echo -e "${GREEN}Test environment has been shut down${NC}" | tee -a "$TEST_LOG" else echo -e "${GREEN}Test environment is still running${NC}" | tee -a "$TEST_LOG" echo "To stop it later, run: docker-compose -f $SCRIPT_DIR/test-env.yml down" | tee -a "$TEST_LOG" fi echo -e "${GREEN}Integration testing completed. See log for details: $TEST_LOG${NC}" EOF chmod +x "$SCRIPT_DIR/docker/test-integration.sh" log "Test integration script created and made executable" fi # Run the integration test log "Running integration test to verify the environment..." "$SCRIPT_DIR/docker/test-integration.sh" # Add entries to package.json for running tests if ! grep -q "test:integration" "$SCRIPT_DIR/package.json"; then log "Adding test integration commands to package.json..." # This approach requires jq, which might not be installed. Using a temporary approach. # Would be better to use jq for proper JSON manipulation sed -i 's/"scripts": {/"scripts": {\n "test:integration": "bash docker\/test-integration.sh",/g' "$SCRIPT_DIR/package.json" fi log "Test environment setup complete!" log "To run integration tests again: cd $SCRIPT_DIR && pnpm test:integration" log "Or directly: $SCRIPT_DIR/docker/test-integration.sh" elif [ "$production_mode" = "y" ] || [ "$production_mode" = "Y" ]; then # Production mode - using Docker log "Setting up in production mode using Docker..." # Create docker-compose file if it doesn't exist if [ ! -f "$SCRIPT_DIR/docker/compose.yml" ]; then log "Creating docker-compose.yml file with network configuration..." cat > "$SCRIPT_DIR/docker/compose.yml" << EOF version: '3.8' services: frontend: image: myp-rp:latest restart: unless-stopped environment: - NODE_ENV=production env_file: - /srv/myp-env/github.env volumes: - /srv/MYP-DB:/app/.next/cache/drizzle networks: - myp-network - backend-network # Network for communicating with backend caddy: image: caddy:2.8 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config networks: - myp-network depends_on: - frontend networks: myp-network: driver: bridge backend-network: # Network for connecting to the backend at 192.168.0.105 driver: bridge ipam: config: - subnet: 192.168.0.0/24 gateway: 192.168.0.1 volumes: caddy_data: caddy_config: EOF log "docker-compose.yml created with backend network configuration" fi # Create Docker Compose for testing environment if it doesn't exist if [ ! -d "$SCRIPT_DIR/cypress" ]; then log "Creating Cypress directory for testing..." mkdir -p "$SCRIPT_DIR/cypress" fi if [ ! -f "$SCRIPT_DIR/cypress/docker-compose.test.yml" ]; then log "Creating docker-compose.test.yml for testing environment..." cat > "$SCRIPT_DIR/cypress/docker-compose.test.yml" << EOF version: '3.8' services: # Backend service backend: build: context: ../../../backend dockerfile: Dockerfile container_name: myp-backend-test ports: - "5000:5000" environment: - SECRET_KEY=testsecretkey123456789 - DATABASE_URL=sqlite:///myp.db - FLASK_ENV=development - TESTING=true volumes: - backend-test-data:/app/instance restart: unless-stopped networks: - test-network # Optional: Frontend test service if needed frontend-test: image: cypress/included:13.6.1 container_name: myp-frontend-test depends_on: - backend environment: - CYPRESS_baseUrl=http://host.docker.internal:3000 - CYPRESS_backendUrl=http://backend:5000 volumes: - ..:/app - ./cypress.config.ts:/app/cypress.config.ts working_dir: /app command: npx cypress run networks: - test-network networks: test-network: driver: bridge volumes: backend-test-data: EOF log "Test environment docker-compose.yml created" fi if [ ! -f "$SCRIPT_DIR/cypress/start-test-environment.sh" ]; then log "Creating test environment startup script..." cat > "$SCRIPT_DIR/cypress/start-test-environment.sh" << 'EOF' #!/bin/bash # Script to start a test environment with backend and optional test runner SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PARENT_DIR="$(dirname "$SCRIPT_DIR")" BACKEND_DIR="$(dirname "$(dirname "$PARENT_DIR")")/backend" # Colors for output GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' NC='\033[0m' # No Color echo -e "${GREEN}Starting MYP test environment...${NC}" echo "Script directory: $SCRIPT_DIR" echo "Frontend directory: $PARENT_DIR" echo "Backend directory: $BACKEND_DIR" # Check if Docker is available if ! command -v docker &> /dev/null; then echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" exit 1 fi # Check if docker-compose is available if ! command -v docker-compose &> /dev/null && ! command -v docker compose &> /dev/null; then echo -e "${RED}Error: Neither docker-compose nor docker compose is available${NC}" exit 1 fi # Function to run docker compose (handles both docker-compose and docker compose syntax) run_docker_compose() { if command -v docker-compose &> /dev/null; then docker-compose "$@" else docker compose "$@" fi } # Check if backend Docker image exists echo -e "${YELLOW}Checking for backend Docker image...${NC}" cd "$SCRIPT_DIR" # Start the backend container echo -e "${GREEN}Starting backend container...${NC}" run_docker_compose -f docker-compose.test.yml up -d backend # Wait for backend to be ready echo -e "${YELLOW}Waiting for backend to be ready...${NC}" max_attempts=30 attempt=1 backend_ready=false while [ $attempt -le $max_attempts ] && [ "$backend_ready" = "false" ]; do echo "Checking backend readiness (attempt $attempt/$max_attempts)..." if curl -s http://localhost:5000/api/health 2>&1 | grep -q "healthy"; then backend_ready=true echo -e "${GREEN}Backend is ready!${NC}" else echo "Backend not ready yet, waiting..." sleep 2 attempt=$((attempt+1)) fi done if [ "$backend_ready" = "false" ]; then echo -e "${RED}Backend failed to start properly after $max_attempts attempts${NC}" echo "Logs from backend container:" run_docker_compose -f docker-compose.test.yml logs backend exit 1 fi # Start frontend development server if it's not already running if ! curl -s http://localhost:3000 > /dev/null; then echo -e "${YELLOW}Starting frontend development server...${NC}" cd "$PARENT_DIR" # Run in background echo "Starting Next.js development server in the background..." nohup pnpm dev > "$SCRIPT_DIR/frontend.log" 2>&1 & # Store the PID for later cleanup FRONTEND_PID=$! echo $FRONTEND_PID > "$SCRIPT_DIR/frontend.pid" echo -e "${GREEN}Frontend development server started with PID $FRONTEND_PID${NC}" echo "Frontend logs available at: $SCRIPT_DIR/frontend.log" # Wait for frontend to be ready echo -e "${YELLOW}Waiting for frontend to be ready...${NC}" max_attempts=30 attempt=1 frontend_ready=false while [ $attempt -le $max_attempts ] && [ "$frontend_ready" = "false" ]; do echo "Checking frontend readiness (attempt $attempt/$max_attempts)..." if curl -s http://localhost:3000 > /dev/null; then frontend_ready=true echo -e "${GREEN}Frontend is ready!${NC}" else echo "Frontend not ready yet, waiting..." sleep 2 attempt=$((attempt+1)) fi done if [ "$frontend_ready" = "false" ]; then echo -e "${RED}Frontend failed to start properly${NC}" exit 1 fi else echo -e "${GREEN}Frontend already running at http://localhost:3000${NC}" fi echo -e "${GREEN}Test environment is ready!${NC}" echo "Backend is available at: http://localhost:5000" echo "Frontend is available at: http://localhost:3000" echo "" echo "To run Cypress tests:" echo " cd $PARENT_DIR && pnpm cypress" echo "" echo "To stop the test environment:" echo " $SCRIPT_DIR/stop-test-environment.sh" EOF chmod +x "$SCRIPT_DIR/cypress/start-test-environment.sh" log "Test environment startup script created and made executable" fi if [ ! -f "$SCRIPT_DIR/cypress/stop-test-environment.sh" ]; then log "Creating test environment shutdown script..." cat > "$SCRIPT_DIR/cypress/stop-test-environment.sh" << 'EOF' #!/bin/bash # Script to stop the test environment SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PARENT_DIR="$(dirname "$SCRIPT_DIR")" # Colors for output GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' NC='\033[0m' # No Color echo -e "${YELLOW}Stopping MYP test environment...${NC}" # Function to run docker compose (handles both docker-compose and docker compose syntax) run_docker_compose() { if command -v docker-compose &> /dev/null; then docker-compose "$@" else docker compose "$@" fi } # Stop the backend container echo "Stopping backend containers..." cd "$SCRIPT_DIR" run_docker_compose -f docker-compose.test.yml down # Stop the frontend development server if we started it if [ -f "$SCRIPT_DIR/frontend.pid" ]; then FRONTEND_PID=$(cat "$SCRIPT_DIR/frontend.pid") echo "Stopping frontend development server (PID: $FRONTEND_PID)..." if kill -0 $FRONTEND_PID 2>/dev/null; then kill $FRONTEND_PID echo "Frontend development server stopped" else echo "Frontend development server is not running with PID $FRONTEND_PID" fi rm -f "$SCRIPT_DIR/frontend.pid" rm -f "$SCRIPT_DIR/frontend.log" else echo "No frontend PID file found, assuming it was started externally" fi echo -e "${GREEN}Test environment has been stopped${NC}" EOF chmod +x "$SCRIPT_DIR/cypress/stop-test-environment.sh" log "Test environment shutdown script created and made executable" fi # Build Docker image with error handling log "Building Docker image (this may take a while)..." # Check available disk space before starting build available_space=$(df -m / | awk 'NR==2 {print $4}') if [ "$available_space" -lt 2000 ]; then log "WARNING: Low disk space (${available_space}MB). Docker build may fail." log "Consider freeing up at least 2GB of space before continuing." read -p "Continue anyway? (y/n): " continue_build if [ "$continue_build" != "y" ] && [ "$continue_build" != "Y" ]; then log "Docker build aborted due to low disk space." exit 1 fi fi # Test Docker is working properly if ! docker info >> "$LOG_FILE" 2>&1; then log "ERROR: Docker is not running or the current user doesn't have permission to use Docker." log "Try running 'sudo systemctl restart docker' or add your user to the docker group with:" log "'sudo usermod -aG docker $USER' and then log out and back in." exit 1 fi # Try to use existing build script if available if [ -f "$SCRIPT_DIR/docker/build.sh" ]; then log "Using existing build script..." cd "$SCRIPT_DIR/docker" if bash build.sh >> "$LOG_FILE" 2>&1; then log "Docker image built successfully using build.sh" else log "ERROR: Docker build failed using build.sh script" log "Attempting direct build as fallback..." cd "$SCRIPT_DIR" if docker build -t myp-rp:latest . >> "$LOG_FILE" 2>&1; then log "Docker image built successfully using direct method" else log "ERROR: Docker build failed. Check the log at $LOG_FILE for details." exit 1 fi fi else # Direct build method log "Building Docker image manually..." cd "$SCRIPT_DIR" if docker build -t myp-rp:latest . >> "$LOG_FILE" 2>&1; then log "Docker image built successfully" else log "ERROR: Docker build failed. Check the log at $LOG_FILE for details." exit 1 fi fi # Verify the image was created if docker image inspect myp-rp:latest >> /dev/null 2>&1; then log "Verified: Docker image myp-rp:latest exists" else log "ERROR: Docker image myp-rp:latest does not exist after build" exit 1 fi # Start the application with error handling log "Starting the application using Docker Compose..." cd "$SCRIPT_DIR/docker" # Ensure we have the compose file if [ ! -f "compose.yml" ]; then log "ERROR: compose.yml not found in $(pwd)" log "Looking for compose file in alternative locations..." find "$SCRIPT_DIR" -name "compose.yml" -o -name "docker-compose.yml" 2>/dev/null | while read compose_file; do log "Found compose file at: $compose_file" done exit 1 fi # Test docker-compose to make sure it's working if ! docker-compose version >> "$LOG_FILE" 2>&1; then log "WARNING: docker-compose command failed. Trying with docker compose (with space)..." if ! docker compose version >> "$LOG_FILE" 2>&1; then log "ERROR: Both docker-compose and docker compose commands failed." log "Please check Docker and Docker Compose installation." exit 1 else # Use docker compose instead log "Using docker compose (with space) command..." if docker compose -f compose.yml up -d >> "$LOG_FILE" 2>&1; then log "Application started successfully with docker compose!" else log "ERROR: Failed to start application with docker compose." log "Check logs with: docker compose -f compose.yml logs" exit 1 fi fi else # Use docker-compose if docker-compose -f compose.yml up -d >> "$LOG_FILE" 2>&1; then log "Application started successfully with docker-compose!" else log "ERROR: Failed to start application with docker-compose." log "Check logs with: docker-compose -f compose.yml logs" exit 1 fi fi # Verify containers are running log "Verifying containers are running..." sleep 5 # Give containers a moment to start running_containers=$(docker ps --format '{{.Names}}' | grep -c "myp\|frontend\|caddy" || echo "0") if [ "$running_containers" -gt 0 ]; then log "Detected $running_containers running containers related to MYP." else log "WARNING: No running containers detected for MYP frontend." log "Check container status with: docker ps -a" fi log "Frontend installed and running in production mode!" log "The application should be accessible at http://localhost" log "Check logs with: docker-compose -f $SCRIPT_DIR/docker/compose.yml logs -f" else # Development mode log "Setting up in development mode..." # Install dependencies log "Installing project dependencies with pnpm..." cd "$SCRIPT_DIR" # Make sure we're in the right directory and pnpm is available log "Current directory: $(pwd)" log "Checking pnpm path: $(which pnpm 2>/dev/null || echo 'pnpm not found in PATH')" # Install dependencies with retry mechanism max_attempts=3 attempt=1 success=false while [ $attempt -le $max_attempts ] && [ "$success" = "false" ]; do log "Attempt $attempt of $max_attempts to install dependencies..." if command_exists pnpm; then if pnpm install >> "$LOG_FILE" 2>&1; then log "Dependencies installed successfully!" success=true else log "WARNING: pnpm install failed on attempt $attempt" fi else log "ERROR: pnpm not found in PATH. Trying to fix..." export PATH="$PATH:$HOME/.local/share/pnpm:$HOME/.pnpm:$(npm global bin)" if ! command_exists pnpm; then log "Attempting to reinstall pnpm..." npm install -g pnpm >> "$LOG_FILE" 2>&1 fi fi attempt=$((attempt+1)) # If we've failed but have more attempts to go, wait a bit before trying again if [ "$success" = "false" ] && [ $attempt -le $max_attempts ]; then log "Waiting 5 seconds before retry..." sleep 5 fi done if [ "$success" = "false" ]; then log "ERROR: Failed to install dependencies after $max_attempts attempts." log "Please check the log file at $LOG_FILE for details." log "You may need to run 'pnpm install' manually in $SCRIPT_DIR" else log "Dependencies successfully installed." fi # Create .env.local file for development if [ ! -f "$SCRIPT_DIR/.env.local" ]; then log "Creating development environment file with static backend URL..." cat > "$SCRIPT_DIR/.env.local" << EOF # Development Environment Variables # GitHub OAuth AUTH_GITHUB_ID=your_github_client_id AUTH_GITHUB_SECRET=your_github_client_secret AUTH_SECRET=$(openssl rand -hex 32) AUTH_TRUST_HOST=true # Backend URL - Static IP for backend NEXT_PUBLIC_BACKEND_URL=http://192.168.0.105:5000 # Test environment CYPRESS_TEST_USER=test-user EOF log "ATTENTION: Development environment file created with generated AUTH_SECRET" log " Backend URL set to http://192.168.0.105:5000" log " Added Cypress test user configuration" log " Please edit $SCRIPT_DIR/.env.local with your actual GitHub OAuth credentials" fi # Set up Cypress testing if it doesn't exist if [ ! -f "$SCRIPT_DIR/cypress.config.ts" ]; then log "Setting up Cypress configuration..." cat > "$SCRIPT_DIR/cypress.config.ts" << EOF import { defineConfig } from 'cypress' export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', supportFile: 'cypress/support/commands.ts', specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', }, component: { devServer: { framework: 'next', bundler: 'webpack', }, }, env: { backendUrl: 'http://localhost:5000', }, }) EOF log "Cypress configuration created" fi # Create Cypress support directory if it doesn't exist if [ ! -d "$SCRIPT_DIR/cypress/support" ]; then log "Creating Cypress support directory..." mkdir -p "$SCRIPT_DIR/cypress/support" fi # Add Cypress commands file if it doesn't exist if [ ! -f "$SCRIPT_DIR/cypress/support/commands.ts" ]; then log "Creating Cypress commands file..." cat > "$SCRIPT_DIR/cypress/support/commands.ts" << EOF // -- This is a parent command -- Cypress.Commands.add('login', () => { // Simulate logged in user without OAuth window.localStorage.setItem('myp:user', JSON.stringify({ id: 'test-user-id', name: 'Test User', email: 'test@example.com', role: 'user' })) }) // -- This is a child command -- Cypress.Commands.add('createPrintJob', (printerId, duration) => { cy.intercept('POST', \`/api/printers/\${printerId}/reserve\`, { id: 'test-job-id', printerId, userId: 'test-user-id', startTime: new Date().toISOString(), endTime: new Date(Date.now() + duration * 60000).toISOString(), status: 'active' }).as('createJob') return cy.wrap('test-job-id') }) declare global { namespace Cypress { interface Chainable { login(): Chainable createPrintJob(printerId: string, duration: number): Chainable } } } EOF log "Cypress commands file created" fi # Create Cypress e2e directory if it doesn't exist if [ ! -d "$SCRIPT_DIR/cypress/e2e" ]; then log "Creating Cypress e2e directory..." mkdir -p "$SCRIPT_DIR/cypress/e2e" fi # Add Cypress example test file if it doesn't exist if [ ! -f "$SCRIPT_DIR/cypress/e2e/home.cy.ts" ]; then log "Creating Cypress example test file..." cat > "$SCRIPT_DIR/cypress/e2e/home.cy.ts" << EOF describe('Homepage', () => { beforeEach(() => { cy.visit('/') }) it('loads the homepage successfully', () => { cy.contains('Manage Your Printers') cy.get('a[href="/printer"]').should('exist') }) it('shows printer cards', () => { cy.get('[class*="w-auto h-36"]').should('exist') }) it('has working navigation', () => { cy.get('header').should('exist') cy.get('a[href="/"]').should('exist') }) }) EOF log "Cypress example test file created" fi # Create systemd service for development mode (if running as root) if [ "$NO_SUDO" = "false" ]; then log "Creating systemd service for development mode..." cat > /etc/systemd/system/myp-frontend-dev.service << EOF [Unit] Description=MYP Frontend Development Service After=network.target [Service] Type=simple User=${SUDO_USER:-$USER} WorkingDirectory=$SCRIPT_DIR ExecStart=$(which pnpm 2>/dev/null || echo "/usr/bin/pnpm") dev Restart=always RestartSec=10 Environment=PATH=/usr/bin:/usr/local/bin:${HOME}/.local/share/pnpm:${HOME}/.local/bin:${HOME}/.nvm/versions/node/$(node -v 2>/dev/null || echo "v20.0.0")/bin [Install] WantedBy=multi-user.target EOF # Reload systemd and enable the service systemctl daemon-reload systemctl enable myp-frontend-dev.service else # Create a user-level service file for systemd if possible log "Creating user-level service file for development mode..." mkdir -p "$HOME/.config/systemd/user" cat > "$HOME/.config/systemd/user/myp-frontend-dev.service" << EOF [Unit] Description=MYP Frontend Development Service After=network.target [Service] Type=simple WorkingDirectory=$SCRIPT_DIR ExecStart=$(which pnpm 2>/dev/null || echo "${HOME}/.local/bin/pnpm") dev Restart=always RestartSec=10 Environment=PATH=/usr/bin:/usr/local/bin:${HOME}/.local/share/pnpm:${HOME}/.local/bin:${HOME}/.nvm/versions/node/$(node -v 2>/dev/null || echo "v20.0.0")/bin [Install] WantedBy=default.target EOF # Create a convenient start script cat > "$SCRIPT_DIR/start-dev-server.sh" << 'EOF' #!/bin/bash # Script to start the development server # Get the directory where this script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Source environment setup if [ -f "$HOME/.bashrc" ]; then source "$HOME/.bashrc" fi # Ensure NVM is loaded if available if [ -d "$HOME/.nvm" ]; then export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" nvm use 20 &>/dev/null || true fi # Add pnpm to PATH if not already there if [ -d "$HOME/.local/share/pnpm" ]; then export PNPM_HOME="$HOME/.local/share/pnpm" export PATH="$PNPM_HOME:$PATH" fi if [ -d "$HOME/.local/bin" ]; then export PATH="$HOME/.local/bin:$PATH" fi # Change to the script directory cd "$SCRIPT_DIR" # Check for pnpm if ! command -v pnpm &>/dev/null; then echo "ERROR: pnpm not found in PATH" echo "Try installing it with: npm install -g pnpm" exit 1 fi # Start the development server echo "Starting MYP frontend development server..." pnpm dev EOF chmod +x "$SCRIPT_DIR/start-dev-server.sh" log_success "Created user-level service and start script" log "To start the development server manually, run: $SCRIPT_DIR/start-dev-server.sh" log "To use systemd user service: systemctl --user enable --now myp-frontend-dev.service" fi log "Installation complete!" log "" log "To start the frontend development service, run: systemctl start myp-frontend-dev" log "To check service status, run: systemctl status myp-frontend-dev" log "To view logs, run: journalctl -u myp-frontend-dev -f" log "" log "For manual development startup, run: cd $SCRIPT_DIR && pnpm dev" log "" log "The application should be accessible at http://localhost:3000 when running" fi # Perform one-time installation of dependencies using pnpm log "Installing project dependencies with pnpm..." # First check that pnpm is definitely in PATH if ! command_exists pnpm; then log_error "pnpm still not found in PATH despite installation attempts" log_error "PATH: $PATH" log_error "Checking for pnpm installation locations..." for pnpm_location in "$HOME/.local/bin/pnpm" "$HOME/.local/share/pnpm/pnpm" "/usr/local/bin/pnpm" "/usr/bin/pnpm"; do if [ -f "$pnpm_location" ]; then log_warning "pnpm found at $pnpm_location but not in PATH" log_warning "Using absolute path to install dependencies..." # Use absolute path to pnpm cd "$SCRIPT_DIR" "$pnpm_location" install >> "$LOG_FILE" 2>&1 PNPM_STATUS=$? if [ $PNPM_STATUS -eq 0 ]; then log_success "Project dependencies installed using absolute pnpm path" else log_error "Failed to install dependencies using absolute pnpm path" fi break fi done else # pnpm is in PATH cd "$SCRIPT_DIR" pnpm install >> "$LOG_FILE" 2>&1 PNPM_STATUS=$? if [ $PNPM_STATUS -eq 0 ]; then log_success "Project dependencies installed" else log_error "Failed to install dependencies" fi fi log "For issues, check the log file at: $LOG_FILE" # Add helpful commands for post-installation management log "===== Post-Installation Information =====" log "Here are some helpful commands for managing your installation:" log "" log "System management:" log " - Check system status: htop, free -h, df -h" log " - Network status: ip a, netstat -tulpn, ss -tulpn" log " - View logs: tail -f /var/log/syslog, journalctl -f" log "" log "Frontend development:" log " - Start development server: $SCRIPT_DIR/start-dev-server.sh" log " - Install missing dependencies: cd $SCRIPT_DIR && pnpm install" log " - Lint code: cd $SCRIPT_DIR && pnpm lint" log " - Format code: cd $SCRIPT_DIR && npx @biomejs/biome format --write ./src" log "" log "Docker management:" log " - List containers: docker ps -a" log " - Container logs: docker logs " log " - Stop containers: docker-compose -f $SCRIPT_DIR/docker/compose.yml down" log " - Start containers: docker-compose -f $SCRIPT_DIR/docker/compose.yml up -d" log " - Restart containers: docker-compose -f $SCRIPT_DIR/docker/compose.yml restart" log "" if [ "$test_environment" = "y" ] || [ "$test_environment" = "Y" ]; then log "Test environment commands:" log " - Run integration test: bash $SCRIPT_DIR/docker/test-integration.sh" log " - View test logs: cat $SCRIPT_DIR/docker/test-integration.log" log " - Stop test environment: docker-compose -f $SCRIPT_DIR/docker/test-env.yml down" else log "Testing environment:" log " - Start test environment: cd $SCRIPT_DIR && pnpm test:start-environment" log " - Run Cypress tests: cd $SCRIPT_DIR && pnpm cypress" log " - Stop test environment: cd $SCRIPT_DIR && pnpm test:stop-environment" log " - To install test environment: $0 --auto-test" fi log "" log "Backend connection:" log " - Test backend connection: curl -I http://192.168.0.105:5000/api/test" log " - Check backend accessibility: ping 192.168.0.105" log "" log "Installation Complete! 🎉"