diff --git a/.gitea/workflows/be-img-build-and-push.yaml b/.gitea/workflows/be-img-build-and-push.yaml new file mode 100644 index 0000000..98d84dd --- /dev/null +++ b/.gitea/workflows/be-img-build-and-push.yaml @@ -0,0 +1,88 @@ +name: Full Build and Docker Push Workflow +run-name: ${{ gitea.actor }} is running a full build and Docker push workflow + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + Builder: + runs-on: catthacker-ubuntu-latest + steps: + - name: Info + run: | + echo "Triggered by ${{ gitea.event_name }} event" + echo "Branch: ${{ gitea.ref }}" + echo "Repository: ${{ gitea.repository }}" + + - name: Update apt-get and install Maven + run: | + apt-get update -y + apt-get install -y maven + + - name: Clone the repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' + + - name: Build with Maven + run: | + java -version + mvn -version + mvn -B clean test -Dspring.profiles.active=test + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + #Build Docker image - Images must follow this naming convention: - {registry}/{owner}/{image} + - name: Build Docker image + env: + GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }} + OWNER: ${{ gitea.actor }} + IMAGE_NAME: my-app + run: | + #docker build -t $GITEA_REGISTRY_URL/$OWNER/my-app:latest . + # registry.domain.com//my-app:latest e.g. git.bepastem.com/eberen/my-app:latest + docker build -t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:latest . + + - name: Build and push Docker image using Buildx + env: + GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }} + IMAGE_NAME: my-app-backend + APP_VERSION: v1.0.0 + OWNER: ${{ gitea.actor }} + LOGIN_TOKEN: ${{ secrets.LOGIN_TOKEN }} + run: | + docker buildx build --platform linux/amd64 -t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:$APP_VERSION . + #docker buildx build \ + # --platform linux/amd64 \ + # #-t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:latest \ + # #--push \ + # . + + - name: Push Docker image to Gitea Registry + env: + GITEA_USERNAME: ${{ gitea.actor }} + LOGIN_TOKEN: ${{ secrets.LOGIN_TOKEN }} + GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }} + run: | + # Login securely + echo "$LOGIN_TOKEN" | docker login $GITEA_REGISTRY_URL --username $GITEA_USERNAME --password-stdin + docker push $GITEA_REGISTRY_URL/$GITEA_USERNAME/my-app:latest + docker logout $GITEA_REGISTRY_URL + + - name: Cleanup + run: | + docker images -a + docker system prune -f + echo "Cleanup done." + diff --git a/.gitea/workflows/checks-and-policy.yml b/.gitea/workflows/checks-and-policy.yml new file mode 100644 index 0000000..ee6c400 --- /dev/null +++ b/.gitea/workflows/checks-and-policy.yml @@ -0,0 +1,209 @@ +name: checks + +on: + push: + pull_request: + +jobs: + frontend-jobs: + name: Set up Node and other necessary dependencies for Frontend Tests and Build + if: ${{ github.ref == 'refs/heads/main' }} # skip + runs-on: ubuntu-latest + + steps: + - name: Info + run: | + echo "Triggered by ${{ gitea.event_name }} event" + echo "Branch: ${{ gitea.ref }}" + echo "Repository: ${{ gitea.repository }}" + + - name: Install Node.js, npm, and Yarn (optimized) + run: | + set -e + echo "Preparing Node.js, npm, and Yarn setup..." + + if command -v apt >/dev/null 2>&1; then + echo "→ Using apt (Debian/Ubuntu)..." + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -y -qq curl ca-certificates >/dev/null + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 + apt-get install -y -qq nodejs >/dev/null + npm install -g yarn --silent + + elif command -v apk >/dev/null 2>&1; then + echo "→ Using apk (Alpine)..." + apk add --no-cache curl nodejs npm >/dev/null + npm install -g yarn --silent + + elif command -v dnf >/dev/null 2>&1; then + echo "→ Using dnf (Fedora/RHEL)..." + dnf install -y -q curl ca-certificates nodejs npm >/dev/null + npm install -g yarn --silent + + else + echo "No supported package manager found (apt, apk, dnf)." + exit 1 + fi + + echo "Node.js, npm, and Yarn installed successfully:" + node -v + npm -v + yarn -v + + - name: Checkout repository manually + env: + TOKEN: ${{ secrets.ACCESS_TOKEN }} + CLONE_URL: ${{ vars.CLONE_URL }} + run: | + echo "Cloning from $CLONE_URL" + echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN" + CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#") + git clone --quiet "$CLONE_URL_WITH_AUTH" . + echo "${{ gitea.repository }} cloned successfully." + + # Install frontend dependencies and run tests + - name: Install dependencies, run tests and build frontend + #uses: actions/cache@v3 + working-directory: ./frontend + with: + path: | + ~/.yarn/cache + ./node_modules + key: frontend-${{ runner.os }}-yarn-${{ hashFiles('frontend/yarn.lock') }} + restore-keys: | + frontend-${{ runner.os }}-yarn- + run: | + yarn install --frozen-lockfile + yarn lint + yarn build + #yarn test --watchAll=false --ci + + # Ensure SSH and SCP are installed and functional + - name: Setup SSH and SCP + run: | + set -e + echo "Ensuring SSH and SCP are available..." + if command -v apt >/dev/null 2>&1; then + apt-get update -qq >/dev/null + apt-get install -y -qq openssh-client >/dev/null + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache openssh >/dev/null + elif command -v dnf >/dev/null 2>&1; then + dnf install -y -q openssh-clients >/dev/null + else + echo "No supported package manager found for SSH installation." + exit 1 + fi + echo "SSH and SCP successfully installed:" + ssh -V + scp -V || echo "SCP version info not available but command exists." + + - name: Publish frontend .next build to Plesk server + env: + SSH_PRIVATE_KEY: ${{ vars.SSH_PRIVATE_KEY }} + SERVER_IP: ${{ vars.SERVER_IP }} + DOMAIN_NAME: ${{ vars.DOMAIN_NAME }} # optional, helps locate vhost + run: | + mkdir -p ~/.ssh + echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + + echo "Deploying to server: $SERVER_IP" + echo "Deploying to domain: $DOMAIN_NAME" + + #Define Plesk web root (update DOMAIN_NAME in Gitea variables) + WEB_ROOT="/var/www/vhosts/${DOMAIN_NAME}/httpdocs" + + cat ~/.ssh/id_rsa | head -3 + + # Convert if it's a PEM key + if grep -q "BEGIN PRIVATE KEY" ~/.ssh/id_rsa; then + echo "Converting PKCS#8 key to OpenSSH-compatible RSA key..." + openssl rsa -in ~/.ssh/id_rsa -out ~/.ssh/id_rsa.openssh >/dev/null 2>&1 + mv ~/.ssh/id_rsa.openssh ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + fi + + echo "SSH key ready for use:" + ssh-keygen -lf ~/.ssh/id_rsa + + ssh -o StrictHostKeyChecking=no root@$SERVER_IP "mkdir -p $WEB_ROOT/.next" + ssh -o StrictHostKeyChecking=no root@$SERVER_IP "mkdir -p $WEB_ROOT/public/.htaccess" + + #Copy only the .next build folder to the server + scp -o StrictHostKeyChecking=no -r frontend/.next root@$SERVER_IP:$WEB_ROOT/ + + echo "Frontend .next build successfully deployed to $SERVER_IP:$WEB_ROOT/.next" + + backend-jobs: + name: Set up Java for Backend Tests and Build + if: ${{ github.ref == 'refs/heads/main' }} # skip + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Java 21 + Maven + run: | + echo "Detecting package manager..." + if command -v apt-get >/dev/null 2>&1; then + PM=apt + elif command -v apk >/dev/null 2>&1; then + PM=apk + elif command -v yum >/dev/null 2>&1; then + PM=yum + else + echo "No known package manager found. Will install JDK manually." + PM=none + fi + + echo "Package manager detected: $PM" + + if [ "$PM" = "apt" ]; then + apt-get update -y + apt-get install -y openjdk-21-jdk maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 + elif [ "$PM" = "apk" ]; then + apk update + apk add openjdk21 maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk + elif [ "$PM" = "yum" ]; then + yum install -y java-21-openjdk-devel maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk + else + # Fallback: manual download + JDK_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz" + mkdir -p /opt/jdk + wget -q -O /tmp/jdk.tar.gz $JDK_URL + tar -xzf /tmp/jdk.tar.gz -C /opt/jdk --strip-components=1 + JAVA_HOME=/opt/jdk + fi + + # Export JAVA_HOME and update PATH for subsequent steps + echo "JAVA_HOME=$JAVA_HOME" >> $GITEA_ENV + echo "$JAVA_HOME/bin" >> $GITEA_PATH + export JAVA_HOME=$JAVA_HOME + export PATH=$JAVA_HOME/bin:$PATH + + # Verify Java and Maven installation + java -version + mvn -version + + - name: Verify Java setup + run: | + java -version + + - name: Verify Java setup + run: | + ls -l $JAVA_HOME/bin/java + file $JAVA_HOME/bin/java || true + java -version + + # Run backend tests and build + - name: Test & build backend + working-directory: . + run: | + mvn clean test -Dspring.profiles.active=test + echo "Backend test and build workflow successfully completed." \ No newline at end of file diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml index d3f1e27..27082ef 100644 --- a/.gitea/workflows/demo.yaml +++ b/.gitea/workflows/demo.yaml @@ -11,6 +11,8 @@ on: jobs: Explore-Gitea-Actions-2: + name: "Explore Gitea Actions - 2" + if: ${{ github.ref == 'refs/heads/main' }} # skip runs-on: ubuntu-latest steps: - name: Info @@ -72,4 +74,39 @@ jobs: yarn -v - name: Done - run: echo "Workflow finished." \ No newline at end of file + run: echo "Workflow finished." + + #blish-backend-docker-image: + #name: Build and publish backend Docker image to server + #runs-on: ubuntu-latest + #needs: backend-jobs + #steps: + # - name: Checkout repository + # uses: actions/checkout@v4 + # + # - name: Set up Docker + # run: | + # apt-get update -y + # apt-get install -y docker.io + # + # - name: Build Docker image + # run: | + # docker build -t backend-app:latest . + # + # - name: Save Docker image as tarball + # run: docker save backend-app:latest -o backend-app.tar + # + # - name: Copy Docker image to remote server and load it + # env: + # SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + # SERVER_IP: + # run: | + # mkdir -p ~/.ssh + # echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa + # chmod 600 ~/.ssh/id_rsa + # scp -o StrictHostKeyChecking=no backend-app.tar root@$SERVER_IP:/tmp/backend-app.tar + # ssh -o StrictHostKeyChecking=no root@$SERVER_IP " + # docker load -i /tmp/backend-app.tar && + # docker tag backend-app:latest backend-app:latest && + # echo 'Backend Docker image loaded successfully' + # " diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 8fc17ec..d481e1e 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -7,6 +7,7 @@ on: jobs: frontend-jobs: name: Set up Node and other necessary dependencies for Frontend Tests and Build + if: ${{ github.ref == 'refs/heads/main' }} # skip runs-on: ubuntu-latest steps: @@ -83,6 +84,7 @@ jobs: backend-jobs: name: Set up Java for Backend Tests and Build + if: ${{ github.ref == 'refs/heads/main' }} # skip runs-on: ubuntu-latest #container: #image: eclipse-temurin:21-jdk diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e9a367 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# ===== Stage 1: Build with Maven ===== +FROM eclipse-temurin:21-jdk-alpine AS builder + +ARG MAVEN_VERSION=3.9.9 +ENV MAVEN_HOME=/opt/maven +ENV PATH=${MAVEN_HOME}/bin:${PATH} + +# Install required tools quietly and set up Maven +RUN set -eux; \ + apk add --no-cache curl tar bash openssl >/dev/null; \ + echo "Downloading Maven ${MAVEN_VERSION}..."; \ + curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o /tmp/maven.tar.gz; \ + mkdir -p /opt >/dev/null; \ + tar -xzf /tmp/maven.tar.gz -C /opt >/dev/null; \ + ln -s /opt/apache-maven-${MAVEN_VERSION} ${MAVEN_HOME}; \ + rm /tmp/maven.tar.gz; \ + echo "✅ Maven ${MAVEN_VERSION} installed successfully." + +WORKDIR /build + +# Copy pom.xml and prefetch dependencies silently +COPY pom.xml . +RUN mvn -B -q dependency:go-offline + +# Copy source code +COPY src ./src + +# Build project (skip tests, quiet mode) +RUN mvn -B -q clean package -DskipTests + +# ===== Stage 2: Runtime image ===== +FROM eclipse-temurin:21-jre-alpine + +RUN addgroup -S spring && adduser -S spring -G spring +USER spring:spring + +WORKDIR /app + +COPY --from=builder /build/target/*.jar app.jar + +EXPOSE 8080 + +# Optional: Health check (Spring Boot actuator) +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD wget -qO- http://localhost:8080/actuator/health | grep '"status":"UP"' || exit 1 + +ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"] \ No newline at end of file diff --git a/build-image.sh b/build-image.sh new file mode 100644 index 0000000..9fb4fea --- /dev/null +++ b/build-image.sh @@ -0,0 +1,8 @@ +#! /bin/bash +set -e # Exit immediately if a command exits with a non-zero status + +export $(grep -v '^#' C:/atrizult/projects/.env | xargs) + +#Build +docker buildx build --no-cache -t eberen/services:backend-${VERSION} . +echo "Docker Image Built Successfully" diff --git a/frontend/public/.htaccess b/frontend/public/.htaccess new file mode 100644 index 0000000..3d5d80c --- /dev/null +++ b/frontend/public/.htaccess @@ -0,0 +1,8 @@ + + RewriteEngine On + RewriteBase / + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.html [L] + \ No newline at end of file diff --git a/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java b/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java index d3cff93..ccaaf15 100644 --- a/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java +++ b/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -@ActiveProfiles("test") // ensures application-test.properties is used +@ActiveProfiles("test") public class StudentServiceImplTest { private final StudentRepository studentRepository = mock(StudentRepository.class);