name: Build, Push and Deploy on: push: branches: [ main, master ] workflow_dispatch: inputs: deploy_to_production: description: 'Deploy to Production?' required: true default: 'no' type: choice options: - 'yes' - 'no' image_tag: description: 'Image tag to deploy (optional, uses latest if empty)' required: false type: string jobs: build-and-push: runs-on: ubuntu-latest outputs: image_tag: ${{ steps.vars.outputs.image_tag }} image_sha: ${{ steps.vars.outputs.image_sha }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set variables id: vars run: | echo "image_tag=$(echo ${{ gitea.sha }} | cut -c1-8)" >> $GITHUB_OUTPUT echo "image_sha=${{ gitea.sha }}" >> $GITHUB_OUTPUT echo "build_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Registry uses: docker/login-action@v3 with: registry: kode.sadateknologi.site username: ${{ gitea.repository_owner }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Build and push image uses: docker/build-push-action@v5 with: context: . push: true tags: | kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:${{ gitea.sha }} kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:${{ steps.vars.outputs.image_tag }} labels: | org.opencontainers.image.created=${{ steps.vars.outputs.build_time }} org.opencontainers.image.revision=${{ gitea.sha }} org.opencontainers.image.version=${{ steps.vars.outputs.image_tag }} deploy-staging: runs-on: ubuntu-latest needs: build-and-push if: gitea.event_name == 'push' && (gitea.ref == 'refs/heads/main' || gitea.ref == 'refs/heads/master') steps: - name: Deploy to Staging (Local) run: | echo "🚀 Starting staging deployment on local server..." # Pull the latest image echo "Pulling image: kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest" docker pull kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest # Stop and remove existing container echo "Stopping existing container..." docker stop kulakpos_web_staging || true docker rm kulakpos_web_staging || true # Clean up old images (keep last 5) docker image prune -f # Run new container echo "Starting new container..." docker run -d \ --restart unless-stopped \ --health-cmd="curl -f http://localhost:8000 || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-retries=3 \ --name kulakpos_web_staging \ --network="host" \ -v /opt/kulakpos/staging/.env:/var/www/html/.env \ -p 8000:8000 \ -e MODE=staging \ -e APP_ENV=staging \ kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:latest # Check if container is running sleep 5 if docker ps | grep -q kulakpos_web_staging; then echo "✅ Staging deployment successful!" docker logs --tail 20 kulakpos_web_staging else echo "❌ Staging deployment failed! Check logs:" docker logs kulakpos_web_staging || echo "No logs available" exit 1 fi - name: Verify Staging Deployment run: | curl -f http://localhost:8000/health || curl -f http://localhost:8000 || echo 'Health check endpoint not found, but service may be running' echo "✅ Staging deployment verified!" deploy-production: runs-on: ubuntu-latest needs: build-and-push if: gitea.event_name == 'workflow_dispatch' && gitea.event.inputs.deploy_to_production == 'yes' environment: production steps: # ============================================ # STEP 1: Setup SSH Key and Configuration # ============================================ - name: Setup SSH Key and Config run: | echo "🔑 Setting up SSH authentication..." mkdir -p ~/.ssh chmod 700 ~/.ssh # Decode the base64-encoded SSH key echo "${{ secrets.PRODUCTION_SSH_KEY_BASE64 }}" | base64 -d > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa # Add host to known_hosts ssh-keyscan -H ${{ secrets.PRODUCTION_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true # Create SSH config cat > ~/.ssh/config << EOF Host kulakpos_prod HostName ${{ secrets.PRODUCTION_HOST }} User ${{ secrets.PRODUCTION_USER }} IdentityFile ~/.ssh/id_rsa StrictHostKeyChecking accept-new EOF chmod 600 ~/.ssh/config echo "✅ SSH configuration complete" - name: Debug SSH Configuration run: | echo "Current user: $(whoami)" echo "Home directory: $HOME" echo "SSH config location: $HOME/.ssh/config" echo "SSH config content:" cat $HOME/.ssh/config || echo "No SSH config found" echo "" echo "SSH key permissions:" ls -la ~/.ssh/ echo "" echo "Testing SSH connection:" ssh -v kulakpos_prod echo "Connection successful" || echo "Connection failed" - name: Determine image tag for production id: prod_image run: | if [ -n "${{ gitea.event.inputs.image_tag }}" ]; then echo "image_tag=${{ gitea.event.inputs.image_tag }}" >> $GITHUB_OUTPUT else echo "image_tag=latest" >> $GITHUB_OUTPUT fi - name: Deploy to Production Server run: | ssh kulakpos_prod << 'EOF' IMAGE_TAG="${{ steps.prod_image.outputs.image_tag }}" echo "🚀 Starting production deployment with image tag: $IMAGE_TAG" # Show current deployment echo "Current production container:" docker ps --filter name=kulakpos_web_prod # Create backup of current deployment info docker inspect kulakpos_web_prod > /tmp/kulakpos_backup_$(date +%Y%m%d_%H%M%S).json || true # Pull the specified image echo "Pulling image: kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" docker pull kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG # Stop and remove existing container echo "Stopping existing container..." docker stop kulakpos_web_prod || true docker rm kulakpos_web_prod || true # Run new container echo "Starting new container..." docker run -d \ --restart unless-stopped \ --health-cmd="curl -f http://localhost:8000 || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-retries=3 \ --name kulakpos_web_prod \ --network="host" \ -v /opt/kulakpos/.env:/var/www/html/.env \ -p 8000:8000 \ -e MODE=prod \ -e APP_ENV=production \ kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG # Check if container is running sleep 10 if docker ps | grep -q kulakpos_web_prod; then echo "✅ Production deployment successful!" echo "Container logs (last 20 lines):" docker logs --tail 20 kulakpos_web_prod else echo "❌ Production deployment failed! Checking logs:" docker logs kulakpos_web_prod || echo "No logs available" # Attempt rollback to previous version if available echo "Attempting rollback to previous version..." PREVIOUS_IMAGE=$(docker inspect kulakpos_web_prod --format='{{.Config.Image}}' 2>/dev/null || echo "") if [ -n "$PREVIOUS_IMAGE" ] && [ "$PREVIOUS_IMAGE" != "kode.sadateknologi.site/${{ gitea.repository_owner }}/kulakpos_web:$IMAGE_TAG" ]; then docker run -d \ --restart unless-stopped \ --name kulakpos_web_prod \ --network="host" \ -v /opt/kulakpos/.env:/var/www/html/.env \ -p 8000:8000 \ -e MODE=prod \ -e APP_ENV=production \ $PREVIOUS_IMAGE echo "Rollback attempted" fi exit 1 fi EOF - name: Verify Production Deployment run: | ssh kulakpos_prod "curl -f http://localhost:8000/health || curl -f http://localhost:8000 || echo 'Service is running but health check endpoint not configured'" echo "✅ Production deployment verified!" - name: Send Deployment Notification if: always() run: | STATUS="${{ job.status }}" if [ "$STATUS" = "success" ]; then echo "✅ Production deployment completed successfully!" else echo "❌ Production deployment failed!" exit 1 fi cleanup: runs-on: ubuntu-latest needs: [deploy-staging, deploy-production] if: always() steps: - name: Cleanup old Docker images on runners run: | docker system prune -f --filter "until=24h"