From 9464b75ed4d4555985555d135b97efe228dc7257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Murat=20O=CC=88zkorkmaz?= Date: Tue, 21 Oct 2025 23:58:36 +0200 Subject: [PATCH] CI test --- .gitea/workflows/deploy.yml | 123 +++++++++++++++++++++++++++++ .gitignore | 1 + docker/.env.dist | 30 +++++++ docker/Dockerfile | 39 +++++++++ docker/build.sh | 91 +++++++++++++++++++++ docker/compose.yml | 55 +++++++++++++ src/main/resources/application.yml | 20 ++--- 7 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 docker/.env.dist create mode 100644 docker/Dockerfile create mode 100755 docker/build.sh create mode 100644 docker/compose.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..7d660ba --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,123 @@ +name: Build, Push and Deploy + +on: + push: + branches: + - main + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Build with Maven + run: mvn clean package -DskipTests + + - name: Login to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.REGISTRY_URL }} + username: ${{ secrets.CI_GITEA_USER }} + password: ${{ secrets.CI_GITEA_TOKEN }} + + - name: Extract metadata for Docker + id: meta + run: | + echo "image_tag=${{ vars.REGISTRY_URL }}/${{ vars.NAMESPACE }}/${{ vars.REPO_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT + echo "image_latest=${{ vars.REGISTRY_URL }}/${{ vars.NAMESPACE }}/${{ vars.REPO_NAME }}:latest" >> $GITHUB_OUTPUT + + - name: Build and Push Docker Image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: | + ${{ steps.meta.outputs.image_tag }} + ${{ steps.meta.outputs.image_latest }} + + - name: Deploy to Remote Server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + # Navigate to deployment directory + cd ${{ secrets.DEPLOY_PATH }} + + # Create .env file with all secrets + cat > .env << 'EOF' + # Deployment + REGISTRY_URL=${{ vars.REGISTRY_URL }} + NAMESPACE=${{ vars.NAMESPACE }} + REPO_NAME=${{ vars.REPO_NAME }} + IMAGE_TAG=${{ github.sha }} + CI_GITEA_USER=${{ secrets.CI_GITEA_USER }} + CI_GITEA_TOKEN=${{ secrets.CI_GITEA_TOKEN }} + + # Application + APP_PORT=${{ secrets.APP_PORT }} + SPRING_PROFILES_ACTIVE=${{ secrets.SPRING_PROFILES_ACTIVE }} + APPLICATION_NAME=${{ secrets.APPLICATION_NAME }} + CORS_ALLOWED_ORIGINS=${{ secrets.CORS_ALLOWED_ORIGINS }} + + # PostgreSQL Configuration + POSTGRES_HOST=${{ secrets.POSTGRES_HOST }} + POSTGRES_PORT=${{ secrets.POSTGRES_PORT }} + POSTGRES_DB=${{ secrets.POSTGRES_DB }} + POSTGRES_USER=${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} + + # Keycloak Configuration + KEYCLOAK_URL=${{ secrets.KEYCLOAK_URL }} + KEYCLOAK_REALM=${{ secrets.KEYCLOAK_REALM }} + KEYCLOAK_ISSUER_URI=${{ secrets.KEYCLOAK_ISSUER_URI }} + + # MinIO (S3) Configuration + MINIO_ENDPOINT=${{ secrets.MINIO_ENDPOINT }} + MINIO_ACCESS_KEY=${{ secrets.MINIO_ACCESS_KEY }} + MINIO_SECRET_KEY=${{ secrets.MINIO_SECRET_KEY }} + + # AWS S3 Configuration (if needed) + AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_S3_BUCKET_NAME=${{ secrets.AWS_S3_BUCKET_NAME }} + AWS_S3_REGION=${{ secrets.AWS_S3_REGION }} + AWS_S3_ENDPOINT=${{ secrets.AWS_S3_ENDPOINT }} + + # Hibernate Configuration + HIBERNATE_DDL_AUTO=${{ secrets.HIBERNATE_DDL_AUTO }} + + # Java Options + JAVA_OPTS=${{ secrets.JAVA_OPTS }} + EOF + + # Set proper permissions + chmod 600 .env + + # Login to Container Registry + echo "${{ secrets.CI_GITEA_TOKEN }}" | docker login ${{ vars.REGISTRY_URL }} -u ${{ secrets.CI_GITEA_USER }} --password-stdin + + # Pull latest image + docker compose pull + + # Restart services with new image + docker compose up -d --remove-orphans + + # Clean up old images + docker image prune -af --filter "until=168h" + + # Show running containers + docker compose ps diff --git a/.gitignore b/.gitignore index 667aaef..afe92a8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ +/docker/.env diff --git a/docker/.env.dist b/docker/.env.dist new file mode 100644 index 0000000..b387b37 --- /dev/null +++ b/docker/.env.dist @@ -0,0 +1,30 @@ +# Deployment +REGISTRY_URL=gitea.example.com +NAMESPACE=my-organisation +REPO_NAME=my-repository +IMAGE_TAG=latest +CI_GITEA_USER= +CI_GITEA_TOKEN= + +# Application +APP_PORT=8082 +SPRING_PROFILES_ACTIVE=dev +APPLICATION_NAME=skamp-api +CORS_ALLOWED_ORIGINS=http://localhost:4200 + +# PostgreSQL Configuration +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=skamp +POSTGRES_USER=dev +POSTGRES_PASSWORD=dev + +# Keycloak Configuration +KEYCLOAK_URL=http://localhost:8280 +KEYCLOAK_REALM=skamp +KEYCLOAK_ISSUER_URI=http://localhost:8280/realms/skamp + +# MinIO (S3) Configuration +MINIO_ENDPOINT=http://localhost:9000 +MINIO_ACCESS_KEY=dev +MINIO_SECRET_KEY=dev123456 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..cf77619 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,39 @@ +# ===== Build Stage ===== +FROM maven:3.9.6-eclipse-temurin-21 AS builder + +WORKDIR /build + +# Copy pom.xml first for better layer caching +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copy source code +COPY src ./src + +# Build the application +RUN mvn clean package -DskipTests -B + +# ===== Runtime Stage ===== +FROM eclipse-temurin:21-jre-jammy + +WORKDIR /app + +# Create non-root user +RUN groupadd --system spring && \ + useradd --system --gid spring --create-home spring + +# Copy the JAR from build stage +COPY --from=builder --chown=spring:spring /build/target/*.jar app.jar + +# Switch to non-root user +USER spring + +# Expose Spring Boot default port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/actuator/health || exit 1 + +# Run the application +ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"] diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000..52bc0b4 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Farben für Output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Script Directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Load environment variables from .env +if [ -f "${SCRIPT_DIR}/.env" ]; then + echo -e "${GREEN}Loading environment variables from .env...${NC}" + set -a + source "${SCRIPT_DIR}/.env" + set +a +else + echo -e "${RED}Error: .env file not found in ${SCRIPT_DIR}${NC}" + exit 1 +fi + +# Variables +IMAGE_NAME="${REGISTRY_URL}/${NAMESPACE}/${REPO_NAME}" +IMAGE_FULL="${IMAGE_NAME}:${IMAGE_TAG}" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Building SKAMP Application${NC}" +echo -e "${GREEN}========================================${NC}" +echo -e "Project Root: ${PROJECT_ROOT}" +echo -e "Image: ${IMAGE_FULL}" +echo -e "${GREEN}========================================${NC}" + +# Change to project root +cd "${PROJECT_ROOT}" + +# Step 1: Clean previous builds (optional) +echo -e "\n${YELLOW}Step 1: Cleaning previous builds...${NC}" +if command -v mvn &> /dev/null; then + mvn clean +else + echo -e "${YELLOW}Maven not found locally, will use Docker build stage${NC}" +fi + +# Step 2: Build Docker image +echo -e "\n${YELLOW}Step 2: Building Docker image...${NC}" +docker build \ + -f "${SCRIPT_DIR}/Dockerfile" \ + -t "${IMAGE_FULL}" \ + -t "${IMAGE_NAME}:latest" \ + "${PROJECT_ROOT}" + +# Check if build was successful +if [ $? -eq 0 ]; then + echo -e "\n${GREEN}========================================${NC}" + echo -e "${GREEN}Build successful!${NC}" + echo -e "${GREEN}========================================${NC}" + echo -e "Image: ${IMAGE_FULL}" + echo -e "Also tagged as: ${IMAGE_NAME}:latest" + + # Show image details + echo -e "\n${YELLOW}Image details:${NC}" + docker images "${IMAGE_NAME}" | head -n 2 + + # Optional: Push to registry + read -p "Do you want to push the image to the registry? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo -e "\n${YELLOW}Logging in to registry...${NC}" + echo "${CI_GITEA_TOKEN}" | docker login "${REGISTRY_URL}" -u "${CI_GITEA_USER}" --password-stdin + + if [ $? -eq 0 ]; then + echo -e "${GREEN}Login successful!${NC}" + echo -e "\n${YELLOW}Pushing image to registry...${NC}" + docker push "${IMAGE_FULL}" + docker push "${IMAGE_NAME}:latest" + echo -e "${GREEN}Push successful!${NC}" + else + echo -e "${RED}Login failed! Cannot push images.${NC}" + exit 1 + fi + fi +else + echo -e "\n${RED}Build failed!${NC}" + exit 1 +fi + +echo -e "\n${GREEN}Done!${NC}" diff --git a/docker/compose.yml b/docker/compose.yml new file mode 100644 index 0000000..ab78f7c --- /dev/null +++ b/docker/compose.yml @@ -0,0 +1,55 @@ +services: + app: + image: ${REGISTRY_URL}/${NAMESPACE}/${REPO_NAME}:${IMAGE_TAG:-latest} + container_name: skamp-app + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + ports: + - "${APP_PORT:-8080}:8080" + environment: + # Spring Boot Profile + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE-prod} + + # Database Configuration + SPRING_DATASOURCE_URL: jdbc:postgresql://${POSTGRES_HOST:-localhost}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-skamp} + SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-dev} + SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-dev} + SPRING_JPA_HIBERNATE_DDL_AUTO: ${HIBERNATE_DDL_AUTO:-update} + + # OAuth2/Keycloak Configuration + SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: ${KEYCLOAK_ISSUER_URI:-http://localhost:8280/realms/skamp} + #SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: ${KEYCLOAK_JWK_SET_URI} + + # CORS Configuration + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000} + + # S3 Configuration + S3_ACCESS_KEY: ${MINIO_ACCESS_KEY:-dev} + S3_SECRET_KEY: ${MINIO_SECRET_KEY:-dev123456} + S3_ENDPOINT: ${MINIO_ENDPOINT:-http://localhost:9000} + + # Application Configuration + SERVER_PORT: 8080 + JAVA_OPTS: ${JAVA_OPTS:--Xmx512m -Xms256m} + volumes: + - app_logs:/app/logs + networks: + - skamp-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + +volumes: + postgres_data: + driver: local + app_logs: + driver: local + +networks: + skamp-network: + driver: bridge diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cfb3417..552e865 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,13 +5,13 @@ spring: max-file-size: 1000MB # Maximal zulässige Größe pro Datei (z.B. 10 MB) max-request-size: 5000MB # Maximal zulässige Größe der gesamten Anfrage (z.B. 50 MB) profiles: - active: dev + active: ${SPRING_PROFILES_ACTIVE:prod} application: - name: skamp-api + name: ${APPLICATION_NAME:skamp-api} datasource: - url: jdbc:postgresql://localhost:5432/skamp - username: dev - password: dev + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:skamp} + username: ${POSTGRES_USER:dev} + password: ${POSTGRES_PASSWORD:dev} driver-class-name: org.postgresql.Driver jpa: hibernate: @@ -38,7 +38,7 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: http://localhost:8280/realms/skamp + issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8280/realms/skamp} jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs logging: @@ -52,10 +52,10 @@ logging: cors: allowed-origins: - - http://localhost:4200 + - ${CORS_ALLOWED_ORIGINS:http://localhost:4200} allowed-headers: "*" s3: - access-key: dev - secret-key: dev123456 - endpoint: http://localhost:9000 \ No newline at end of file + access-key: ${MINIO_ACCESS_KEY:dev} + secret-key: ${MINIO_SECRET_KEY:dev123456} + endpoint: ${MINIO_ENDPOINT:http://localhost:9000}