diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..05a54565 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.git +.github +.gradle +build/ +out/ +node_modules/ +src/main/webapp/node_modules/ +src/main/webapp/dist/ +*.iml +.idea/ +Dockerfile +docker-compose.yml +docker/postgres_data/ +.DS_Store diff --git a/README.md b/README.md index 6b9ccbd2..d7a81cbc 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,56 @@ Harmonia is built as a Java/Spring-based monolith, prioritizing stability and jo --- -## 🐳 Docker Command for PostgreSQL +## 🐳 Run Harmonia with Docker -Use this command to quickly set up a local instance of the PostgreSQL database required by the server. +### Quick Start with Startup Scripts + +Use the provided startup scripts for an automated setup: + +**macOS/Linux:** +```bash +./start.sh +``` + +**Windows (PowerShell):** +```powershell +.\start.ps1 +``` + +The scripts will: +- Check if Docker is running +- Clean up any existing containers +- Build and start all services (database, server, client) +- Wait for services to be ready +- Display access URLs and helpful commands + +### Full stack (database + server + client) + +Alternatively, you can manually build and launch everything: + +Alternatively, you can manually build and launch everything: + +1. Build and launch: ```bash - docker compose -f docker/docker-compose.yml up -d + docker compose -f docker/docker-compose.yml up --build ``` +2. Access the services: + * Client (served by nginx): http://localhost:5173 + * Spring Boot server: http://localhost:8080 + * PostgreSQL: localhost:5432 (user `postgres`, password `harmonia`) + +The Compose setup builds the Gradle boot jar inside `docker/server.Dockerfile`, bundles the React client with Vite via `docker/client.Dockerfile`, and proxies `/api` + `/actuator` calls from nginx to the server container. All images restart automatically unless stopped. + +### Database only + +If you only need the database for local development you can start just the PostgreSQL service: + +```bash +docker compose -f docker/docker-compose.yml up -d postgres +``` + ## 🚀 How to Run the Server The application uses Gradle for dependency management and building. Assuming you have **Java 25** installed, use the @@ -129,4 +171,4 @@ This will generate fully typed API clients and models in: src/main/webapp/src/app/generated/ -These files are overwritten each time you run the generator and should be committed to version control to keep client and server in sync. \ No newline at end of file +These files are overwritten each time you run the generator and should be committed to version control to keep client and server in sync. diff --git a/docker/client.Dockerfile b/docker/client.Dockerfile new file mode 100644 index 00000000..d2d85741 --- /dev/null +++ b/docker/client.Dockerfile @@ -0,0 +1,24 @@ +# Build the React/Vite client +FROM node:22-bookworm-slim AS builder +WORKDIR /app + +COPY src/main/webapp/package*.json ./ +RUN npm ci + +COPY src/main/webapp . + +ARG VITE_API_BASE_URL=http://localhost:8080 +ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} +ENV NODE_ENV=production + +RUN npm run build + +# Serve the production build with nginx +FROM nginx:1.27-alpine AS runner + +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index a497c23b..e26fd427 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,4 +1,5 @@ -version: '3.9' +name: harmonia + services: postgres: image: postgres:18.0 @@ -12,6 +13,41 @@ services: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + server: + build: + context: .. + dockerfile: docker/server.Dockerfile + container_name: harmonia-server + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/harmonia + SPRING_DATASOURCE_USERNAME: postgres + SPRING_DATASOURCE_PASSWORD: harmonia + ports: + - "8080:8080" + + client: + build: + context: .. + dockerfile: docker/client.Dockerfile + args: + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:8080} + container_name: harmonia-client + restart: unless-stopped + depends_on: + server: + condition: service_started + ports: + - "5173:80" volumes: postgres_data: diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 00000000..b6431561 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/plain application/xml text/css application/javascript application/json image/svg+xml; + + location / { + try_files $uri /index.html; + } + + location /api/ { + proxy_pass http://harmonia-server:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /actuator/ { + proxy_pass http://harmonia-server:8080/actuator/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/docker/server.Dockerfile b/docker/server.Dockerfile new file mode 100644 index 00000000..417e548e --- /dev/null +++ b/docker/server.Dockerfile @@ -0,0 +1,20 @@ +# Build the Spring Boot application +FROM eclipse-temurin:25-jdk AS builder +WORKDIR /app + +COPY . . + +RUN chmod +x gradlew +RUN ./gradlew --no-daemon bootJar -x test \ + && JAR_FILE=$(find build/libs -maxdepth 1 -type f -name "*.jar" ! -name "*-plain.jar" | head -n 1) \ + && cp "${JAR_FILE}" /app/application.jar + +# Run the packaged jar with a lightweight JRE +FROM eclipse-temurin:25-jre +WORKDIR /app + +COPY --from=builder /app/application.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 00000000..c589cb0b --- /dev/null +++ b/start.ps1 @@ -0,0 +1,151 @@ +# Harmonia Startup Script (Windows PowerShell) +# This script starts all services using Docker containers + +$ErrorActionPreference = "Stop" + +# Colors for output +function Write-Green { Write-Host $args -ForegroundColor Green } +function Write-Blue { Write-Host $args -ForegroundColor Blue } +function Write-Yellow { Write-Host $args -ForegroundColor Yellow } +function Write-Red { Write-Host $args -ForegroundColor Red } + +Write-Blue "========================================" +Write-Blue " Starting Harmonia Application" +Write-Blue " (Containerized Version)" +Write-Blue "========================================" +Write-Host "" + +# Step 1: Check Docker is running +Write-Green "[1/4] Checking Docker..." +try { + docker info | Out-Null + Write-Green "✓ Docker is running" + Write-Host "" +} catch { + Write-Red "✗ Docker is not running. Please start Docker Desktop." + exit 1 +} + +# Step 2: Clean up any existing containers +Write-Green "[2/4] Cleaning up existing containers..." +docker compose -f docker/docker-compose.yml down --remove-orphans 2>$null +Write-Green "✓ Cleanup completed" +Write-Host "" + +# Step 3: Build and start all services +Write-Green "[3/4] Building and starting all services..." +Write-Yellow "This may take several minutes for the first run..." +Write-Yellow "Building Docker images and starting containers..." +Write-Host "" + +docker compose -f docker/docker-compose.yml up --build -d + +if ($LASTEXITCODE -eq 0) { + Write-Green "✓ All services started successfully" + Write-Host "" +} else { + Write-Red "✗ Failed to start services" + Write-Yellow "Check logs with: docker compose -f docker/docker-compose.yml logs" + exit 1 +} + +# Step 4: Wait for services to be ready +Write-Green "[4/4] Waiting for services to be ready..." +Write-Yellow "Checking service health..." + +# Wait for postgres to be healthy +Write-Yellow "• Waiting for PostgreSQL..." +$timeout = 60 +$postgresReady = $false +while ($timeout -gt 0) { + $status = docker compose -f docker/docker-compose.yml ps postgres 2>$null | Select-String "healthy" + if ($status) { + Write-Green " ✓ PostgreSQL is ready" + $postgresReady = $true + break + } + Start-Sleep -Seconds 2 + $timeout -= 2 +} + +if (-not $postgresReady) { + Write-Red " ✗ PostgreSQL failed to start within 60 seconds" + exit 1 +} + +# Wait for server to be responding +Write-Yellow "• Waiting for Spring Boot server..." +$timeout = 120 +$serverReady = $false +while ($timeout -gt 0) { + try { + $response = Invoke-WebRequest -Uri "http://localhost:8080/actuator/health" -UseBasicParsing -TimeoutSec 2 -ErrorAction SilentlyContinue + if ($response.StatusCode -eq 200 -or $response.StatusCode -eq 401) { + Write-Green " ✓ Server is ready" + $serverReady = $true + break + } + } catch { + # Server not ready yet + } + Start-Sleep -Seconds 3 + $timeout -= 3 +} + +if (-not $serverReady) { + Write-Yellow " ⚠ Server may still be starting (timeout reached)" +} + +# Check if client is responding +Write-Yellow "• Waiting for React client..." +$timeout = 30 +$clientReady = $false +while ($timeout -gt 0) { + try { + $response = Invoke-WebRequest -Uri "http://localhost:5173" -UseBasicParsing -TimeoutSec 2 -ErrorAction SilentlyContinue + if ($response.StatusCode -eq 200) { + Write-Green " ✓ Client is ready" + $clientReady = $true + break + } + } catch { + # Client not ready yet + } + Start-Sleep -Seconds 2 + $timeout -= 2 +} + +if (-not $clientReady) { + Write-Yellow " ⚠ Client may still be starting (timeout reached)" +} + +# Summary +Write-Host "" +Write-Blue "========================================" +Write-Green "✓ Harmonia is now running!" +Write-Blue "========================================" +Write-Yellow "Server:" -NoNewline +Write-Host " http://localhost:8080" +Write-Yellow "Client:" -NoNewline +Write-Host " http://localhost:5173" +Write-Yellow "Database:" -NoNewline +Write-Host " PostgreSQL on localhost:5432" +Write-Host "" +Write-Yellow "Useful commands:" +Write-Yellow "• View logs:" -NoNewline +Write-Host " docker compose -f docker/docker-compose.yml logs -f" +Write-Yellow "• Stop services:" -NoNewline +Write-Host " docker compose -f docker/docker-compose.yml down" +Write-Yellow "• Restart services:" -NoNewline +Write-Host " docker compose -f docker/docker-compose.yml restart" +Write-Host "" +Write-Yellow "Press Ctrl+C to stop all services" +Write-Host "" + +# Keep script running and handle Ctrl+C +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + +# Cleanup on exit +Write-Host "" +Write-Red "Shutting down all services..." +docker compose -f docker/docker-compose.yml down diff --git a/start.sh b/start.sh new file mode 100755 index 00000000..46c7fc06 --- /dev/null +++ b/start.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# Harmonia Startup Script +# This script starts all services using Docker containers + +set -e # Exit on error + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Starting Harmonia Application${NC}" +echo -e "${BLUE} (Containerized Version)${NC}" +echo -e "${BLUE}========================================${NC}\n" + +# Step 1: Check Docker is running +echo -e "${GREEN}[1/4] Checking Docker...${NC}" +if ! docker info >/dev/null 2>&1; then + echo -e "${RED}✗ Docker is not running. Please start Docker Desktop.${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Docker is running${NC}\n" + +# Step 2: Clean up any existing containers +echo -e "${GREEN}[2/4] Cleaning up existing containers...${NC}" +docker compose -f docker/docker-compose.yml down --remove-orphans 2>/dev/null || true +echo -e "${GREEN}✓ Cleanup completed${NC}\n" + +# Step 3: Build and start all services +echo -e "${GREEN}[3/4] Building and starting all services...${NC}" +echo -e "${YELLOW}This may take several minutes for the first run...${NC}" +echo -e "${YELLOW}Building Docker images and starting containers...${NC}\n" + +# Start services in background and capture output +docker compose -f docker/docker-compose.yml up --build -d + +if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ All services started successfully${NC}\n" +else + echo -e "${RED}✗ Failed to start services${NC}" + echo -e "${YELLOW}Check logs with: docker compose -f docker/docker-compose.yml logs${NC}" + exit 1 +fi + +# Step 4: Wait for services to be ready +echo -e "${GREEN}[4/4] Waiting for services to be ready...${NC}" +echo -e "${YELLOW}Checking service health...${NC}" + +# Wait for postgres to be healthy +echo -e "${YELLOW}• Waiting for PostgreSQL...${NC}" +timeout=60 +while [ $timeout -gt 0 ]; do + if docker compose -f docker/docker-compose.yml ps postgres | grep -q "healthy"; then + echo -e "${GREEN} ✓ PostgreSQL is ready${NC}" + break + fi + sleep 2 + ((timeout-=2)) +done + +if [ $timeout -le 0 ]; then + echo -e "${RED} ✗ PostgreSQL failed to start within 60 seconds${NC}" + exit 1 +fi + +# Wait for server to be responding +echo -e "${YELLOW}• Waiting for Spring Boot server...${NC}" +timeout=120 +while [ $timeout -gt 0 ]; do + if curl -f -s http://localhost:8080/actuator/health >/dev/null 2>&1; then + echo -e "${GREEN} ✓ Server is ready${NC}" + break + fi + sleep 3 + ((timeout-=3)) +done + +if [ $timeout -le 0 ]; then + echo -e "${YELLOW} ⚠ Server may still be starting (timeout reached)${NC}" +fi + +# Check if client is responding +echo -e "${YELLOW}• Waiting for React client...${NC}" +timeout=30 +while [ $timeout -gt 0 ]; do + if curl -f -s http://localhost:5173 >/dev/null 2>&1; then + echo -e "${GREEN} ✓ Client is ready${NC}" + break + fi + sleep 2 + ((timeout-=2)) +done + +if [ $timeout -le 0 ]; then + echo -e "${YELLOW} ⚠ Client may still be starting (timeout reached)${NC}" +fi + +# Summary +echo -e "\n${BLUE}========================================${NC}" +echo -e "${GREEN}✓ Harmonia is now running!${NC}" +echo -e "${BLUE}========================================${NC}" +echo -e "${YELLOW}Server:${NC} http://localhost:8080" +echo -e "${YELLOW}Client:${NC} http://localhost:5173" +echo -e "${YELLOW}Database:${NC} PostgreSQL on localhost:5432" +echo -e "\n${YELLOW}Useful commands:${NC}" +echo -e "${YELLOW}• View logs:${NC} docker compose -f docker/docker-compose.yml logs -f" +echo -e "${YELLOW}• Stop services:${NC} docker compose -f docker/docker-compose.yml down" +echo -e "${YELLOW}• Restart services:${NC} docker compose -f docker/docker-compose.yml restart" +echo -e "\n${YELLOW}Press Ctrl+C to stop all services${NC}\n" + +# Keep script running and handle Ctrl+C +trap "echo -e '\n${RED}Shutting down all services...${NC}'; docker compose -f docker/docker-compose.yml down; exit" INT + +# Follow logs to keep script running +docker compose -f docker/docker-compose.yml logs -f