name: CI/CD Pipeline (Using Gitea Variables & Secrets) on: push: branches: [ dev, main, production ] env: NODE_VERSION: '20' DOCKER_IMAGE: portfolio-app CONTAINER_NAME: portfolio-app jobs: production: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run tests run: npm run test:production - name: Build application run: npm run build - name: Run security scan run: | echo "๐Ÿ” Running npm audit..." npm audit --audit-level=high || echo "โš ๏ธ Some vulnerabilities found, but continuing..." - name: Verify Gitea Variables and Secrets run: | echo "๐Ÿ” Verifying Gitea Variables and Secrets..." # Check Variables if [ -z "${{ vars.NEXT_PUBLIC_BASE_URL }}" ]; then echo "โŒ NEXT_PUBLIC_BASE_URL variable is missing!" echo "Please set this variable in Gitea repository settings" exit 1 fi if [ -z "${{ vars.MY_EMAIL }}" ]; then echo "โŒ MY_EMAIL variable is missing!" echo "Please set this variable in Gitea repository settings" exit 1 fi if [ -z "${{ vars.MY_INFO_EMAIL }}" ]; then echo "โŒ MY_INFO_EMAIL variable is missing!" echo "Please set this variable in Gitea repository settings" exit 1 fi # Check Secrets if [ -z "${{ secrets.MY_PASSWORD }}" ]; then echo "โŒ MY_PASSWORD secret is missing!" echo "Please set this secret in Gitea repository settings" exit 1 fi if [ -z "${{ secrets.MY_INFO_PASSWORD }}" ]; then echo "โŒ MY_INFO_PASSWORD secret is missing!" echo "Please set this secret in Gitea repository settings" exit 1 fi if [ -z "${{ secrets.ADMIN_BASIC_AUTH }}" ]; then echo "โŒ ADMIN_BASIC_AUTH secret is missing!" echo "Please set this secret in Gitea repository settings" exit 1 fi echo "โœ… All required Gitea variables and secrets are present" echo "๐Ÿ“ Variables found:" echo " - NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }}" echo " - MY_EMAIL: ${{ vars.MY_EMAIL }}" echo " - MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}" echo " - NODE_ENV: ${{ vars.NODE_ENV }}" echo " - LOG_LEVEL: ${{ vars.LOG_LEVEL }}" - name: Build Docker image run: | echo "๐Ÿ—๏ธ Building Docker image..." docker build -t ${{ env.DOCKER_IMAGE }}:latest . docker tag ${{ env.DOCKER_IMAGE }}:latest ${{ env.DOCKER_IMAGE }}:$(date +%Y%m%d-%H%M%S) echo "โœ… Docker image built successfully" - name: Deploy using Gitea Variables and Secrets run: | # Determine if this is staging or production if [ "${{ github.ref }}" == "refs/heads/dev" ] || [ "${{ github.ref }}" == "refs/heads/main" ]; then echo "๐Ÿš€ Deploying Staging using Gitea Variables and Secrets..." COMPOSE_FILE="docker-compose.staging.yml" HEALTH_PORT="3002" CONTAINER_NAME="portfolio-app-staging" DEPLOY_ENV="staging" else echo "๐Ÿš€ Deploying Production using Gitea Variables and Secrets..." COMPOSE_FILE="docker-compose.production.yml" HEALTH_PORT="3000" CONTAINER_NAME="portfolio-app" DEPLOY_ENV="production" fi echo "๐Ÿ“ Using Gitea Variables and Secrets:" echo " - NODE_ENV: ${DEPLOY_ENV}" echo " - LOG_LEVEL: ${LOG_LEVEL}" echo " - NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL}" echo " - MY_EMAIL: ${MY_EMAIL}" echo " - MY_INFO_EMAIL: ${MY_INFO_EMAIL}" echo " - MY_PASSWORD: [SET FROM GITEA SECRET]" echo " - MY_INFO_PASSWORD: [SET FROM GITEA SECRET]" echo " - ADMIN_BASIC_AUTH: [SET FROM GITEA SECRET]" echo " - N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-}" # Stop old containers (only for the environment being deployed) echo "๐Ÿ›‘ Stopping old ${DEPLOY_ENV} containers..." docker compose -f $COMPOSE_FILE down || true # Clean up orphaned containers echo "๐Ÿงน Cleaning up orphaned ${DEPLOY_ENV} containers..." docker compose -f $COMPOSE_FILE down --remove-orphans || true # Start new containers echo "๐Ÿš€ Starting new ${DEPLOY_ENV} containers..." docker compose -f $COMPOSE_FILE up -d --force-recreate # Wait a moment for containers to start echo "โณ Waiting for ${DEPLOY_ENV} containers to start..." sleep 15 # Check container logs for debugging echo "๐Ÿ“‹ ${DEPLOY_ENV} container logs (first 30 lines):" docker compose -f $COMPOSE_FILE logs --tail=30 echo "โœ… ${DEPLOY_ENV} deployment completed!" env: NODE_ENV: ${{ vars.NODE_ENV || 'production' }} LOG_LEVEL: ${{ vars.LOG_LEVEL || 'info' }} NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }} NEXT_PUBLIC_UMAMI_URL: ${{ vars.NEXT_PUBLIC_UMAMI_URL }} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }} MY_EMAIL: ${{ vars.MY_EMAIL }} MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }} MY_PASSWORD: ${{ secrets.MY_PASSWORD }} MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }} ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }} N8N_WEBHOOK_URL: ${{ vars.N8N_WEBHOOK_URL || '' }} N8N_SECRET_TOKEN: ${{ secrets.N8N_SECRET_TOKEN || '' }} - name: Wait for containers to be ready run: | # Determine environment if [ "${{ github.ref }}" == "refs/heads/dev" ] || [ "${{ github.ref }}" == "refs/heads/main" ]; then COMPOSE_FILE="docker-compose.staging.yml" HEALTH_PORT="3002" CONTAINER_NAME="portfolio-app-staging" DEPLOY_ENV="staging" else COMPOSE_FILE="docker-compose.production.yml" HEALTH_PORT="3000" CONTAINER_NAME="portfolio-app" DEPLOY_ENV="production" fi echo "โณ Waiting for ${DEPLOY_ENV} containers to be ready..." sleep 30 # Check if all containers are running echo "๐Ÿ“Š Checking ${DEPLOY_ENV} container status..." docker compose -f $COMPOSE_FILE ps # Wait for application container to be healthy echo "๐Ÿฅ Waiting for ${DEPLOY_ENV} application container to be healthy..." for i in {1..40}; do if curl -f http://localhost:${HEALTH_PORT}/api/health > /dev/null 2>&1; then echo "โœ… ${DEPLOY_ENV} application container is healthy!" break fi echo "โณ Waiting for ${DEPLOY_ENV} application container... ($i/40)" sleep 3 done # Additional wait for main page to be accessible echo "๐ŸŒ Waiting for ${DEPLOY_ENV} main page to be accessible..." for i in {1..20}; do if curl -f http://localhost:${HEALTH_PORT}/ > /dev/null 2>&1; then echo "โœ… ${DEPLOY_ENV} main page is accessible!" break fi echo "โณ Waiting for ${DEPLOY_ENV} main page... ($i/20)" sleep 2 done - name: Health check run: | # Determine environment if [ "${{ github.ref }}" == "refs/heads/dev" ] || [ "${{ github.ref }}" == "refs/heads/main" ]; then COMPOSE_FILE="docker-compose.staging.yml" HEALTH_PORT="3002" CONTAINER_NAME="portfolio-app-staging" DEPLOY_ENV="staging" else COMPOSE_FILE="docker-compose.production.yml" HEALTH_PORT="3000" CONTAINER_NAME="portfolio-app" DEPLOY_ENV="production" fi echo "๐Ÿ” Running comprehensive ${DEPLOY_ENV} health checks..." # Check container status echo "๐Ÿ“Š ${DEPLOY_ENV} container status:" docker compose -f $COMPOSE_FILE ps # Check application container echo "๐Ÿฅ Checking ${DEPLOY_ENV} application container..." if curl -f http://localhost:${HEALTH_PORT}/api/health; then echo "โœ… ${DEPLOY_ENV} application health check passed!" else echo "โš ๏ธ ${DEPLOY_ENV} application health check failed, but continuing..." docker compose -f $COMPOSE_FILE logs --tail=50 # Don't exit 1 for staging, only for production if [ "$DEPLOY_ENV" == "production" ]; then exit 1 fi fi # Check main page if curl -f http://localhost:${HEALTH_PORT}/ > /dev/null; then echo "โœ… ${DEPLOY_ENV} main page is accessible!" else echo "โš ๏ธ ${DEPLOY_ENV} main page check failed, but continuing..." if [ "$DEPLOY_ENV" == "production" ]; then exit 1 fi fi echo "โœ… ${DEPLOY_ENV} health checks completed!" - name: Cleanup old images run: | echo "๐Ÿงน Cleaning up old images..." docker image prune -f docker system prune -f echo "โœ… Cleanup completed"