name: Build and Deploy to Synology NAS on: push: branches: - main - develop pull_request: branches: - main env: DOCKER_IMAGE: laravel-app DOCKER_REGISTRY: ${{ secrets.SYNOLOGY_HOST }}:5000 # Synology Docker Registry Port jobs: # Job 1: Run Tests test: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 with: fetch-depth: 1 - name: Validate Conventional Commit (subject line only) id: validate run: | SUBJECT="$(git log -1 --pretty=%s)" echo "Commit subject: $SUBJECT" if echo "$SUBJECT" | grep -Eq '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+'; then echo "valid=true" >> "$GITHUB_OUTPUT" else echo "valid=false" >> "$GITHUB_OUTPUT" fi - name: Setup PHP if: ${{ steps.validate.outputs.valid == 'true' }} uses: shivammathur/setup-php@v2 with: php-version: '8.4' extensions: mbstring, xml, ctype, json, bcmath, pdo, pdo_sqlite coverage: none - name: Install Composer Dependencies if: ${{ steps.validate.outputs.valid == 'true' }} run: composer install --prefer-dist --no-progress --no-suggest - name: Setup Node.js if: ${{ steps.validate.outputs.valid == 'true' }} uses: actions/setup-node@v4 with: node-version: '20' - name: Install NPM Dependencies if: ${{ steps.validate.outputs.valid == 'true' }} run: npm ci - name: Build Frontend Assets if: ${{ steps.validate.outputs.valid == 'true' }} run: npm run build - name: Run Tests if: ${{ steps.validate.outputs.valid == 'true' }} run: php artisan test --exclude-group=backend-db # Job 2: Build and Push Docker Image build: runs-on: ubuntu-latest needs: test if: github.event_name == 'push' steps: - name: Checkout Code uses: actions/checkout@v4 - name: Validate Conventional Commit id: validate run: | SUBJECT="$(git log -1 --pretty=%s)" echo "Commit subject: $SUBJECT" if echo "$SUBJECT" | grep -Eq '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+'; then echo "valid=true" >> "$GITHUB_OUTPUT" else echo "valid=false" >> "$GITHUB_OUTPUT" fi - name: Stop if not a Conventional Commit if: ${{ steps.validate.outputs.valid != 'true' }} run: | echo "Latest commit is not a valid Conventional Commit. Skipping build." exit 0 - name: Set up Docker Buildx if: ${{ steps.validate.outputs.valid == 'true' }} uses: docker/setup-buildx-action@v3 - name: Determine Docker Tag id: docker_tag if: ${{ steps.validate.outputs.valid == 'true' }} run: | if [ "${{ github.ref_name }}" == "main" ]; then echo "tag=latest" >> "$GITHUB_OUTPUT" echo "env=production" >> "$GITHUB_OUTPUT" else echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" echo "env=staging" >> "$GITHUB_OUTPUT" fi - name: Build Docker Image if: ${{ steps.validate.outputs.valid == 'true' }} uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile push: false load: true tags: ${{ env.DOCKER_IMAGE }}:${{ steps.docker_tag.outputs.tag }} cache-from: type=gha cache-to: type=gha,mode=max - name: Save Docker Image if: ${{ steps.validate.outputs.valid == 'true' }} run: | docker save ${{ env.DOCKER_IMAGE }}:${{ steps.docker_tag.outputs.tag }} | gzip > laravel-app.tar.gz - name: Upload Docker Image Artifact if: ${{ steps.validate.outputs.valid == 'true' }} uses: actions/upload-artifact@v4 with: name: docker-image path: laravel-app.tar.gz retention-days: 1 # Job 3: Deploy to Synology NAS deploy: runs-on: ubuntu-latest needs: build if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop') steps: - name: Checkout Code uses: actions/checkout@v4 - name: Download Docker Image Artifact uses: actions/download-artifact@v4 with: name: docker-image - name: Determine Environment id: env run: | if [ "${{ github.ref_name }}" == "main" ]; then echo "env=production" >> "$GITHUB_OUTPUT" else echo "env=staging" >> "$GITHUB_OUTPUT" fi - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.SYNOLOGY_SSH_KEY }}" > ~/.ssh/synology_key chmod 600 ~/.ssh/synology_key ssh-keyscan -H ${{ secrets.SYNOLOGY_HOST }} >> ~/.ssh/known_hosts - name: Copy Files to Synology run: | # Transfer Docker Image scp -i ~/.ssh/synology_key laravel-app.tar.gz \ ${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }}:/volume1/docker/laravel-app/ # Transfer Docker Compose file scp -i ~/.ssh/synology_key docker-compose.synology.yml \ ${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }}:/volume1/docker/laravel-app/ # Transfer deployment script scp -i ~/.ssh/synology_key scripts/deploy-synology.sh \ ${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }}:/volume1/docker/laravel-app/ - name: Deploy on Synology run: | ssh -i ~/.ssh/synology_key \ ${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \ "cd /volume1/docker/laravel-app && chmod +x deploy-synology.sh && ./deploy-synology.sh" - name: Health Check run: | sleep 10 # Wait for container to start # Check if the app is responding SYNOLOGY_IP="${{ secrets.SYNOLOGY_HOST }}" MAX_RETRIES=30 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do if curl -f -s http://$SYNOLOGY_IP:8080/health > /dev/null 2>&1; then echo "✅ Application is healthy!" exit 0 fi echo "⏳ Waiting for application to be ready... ($RETRY_COUNT/$MAX_RETRIES)" sleep 5 RETRY_COUNT=$((RETRY_COUNT + 1)) done echo "❌ Health check failed after $MAX_RETRIES attempts" exit 1 - name: Cleanup if: always() run: | rm -f ~/.ssh/synology_key ssh -i ~/.ssh/synology_key \ ${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \ "rm -f /volume1/docker/laravel-app/laravel-app.tar.gz" || true # Job 4: Notify on Success/Failure (optional) notify: runs-on: ubuntu-latest needs: [test, build, deploy] if: always() steps: - name: Deployment Status run: | if [ "${{ needs.deploy.result }}" == "success" ]; then echo "✅ Deployment to Synology NAS successful!" echo "🌐 Application available at: http://${{ secrets.SYNOLOGY_HOST }}:8080" else echo "❌ Deployment failed. Check the logs for details." exit 1 fi