# Gitea CI/CD for Laravel 12 API # # Triggers: # - Push to any branch # - Pull requests # - Tags # # Stages: validate → test → build → security name: API CI/CD on: push: branches: [main, master, develop, 'feature/**', 'fix/**'] tags: ['v*'] pull_request: branches: [main, master, develop] env: APP_ENV: testing APP_DEBUG: false APP_KEY: "" LOG_CHANNEL: stderr LOG_LEVEL: debug DB_CONNECTION: sqlite DB_DATABASE: ${{ github.workspace }}/database/database.sqlite CACHE_DRIVER: array SESSION_DRIVER: array QUEUE_CONNECTION: sync MAIL_MAILER: array FILESYSTEM_DISK: local COMPOSER_CACHE_DIR: ${{ github.workspace }}/.composer-cache jobs: # ────────────────────────────────────────────── # VALIDATE: composer validate + Laravel Pint # ────────────────────────────────────────────── validate: name: Validate (composer + pint) runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 steps: - name: Checkout repository run: | apt-get update apt-get install -y --no-install-recommends git ca-certificates git clone --depth 1 https://gitea:${{ secrets.GITHUB_TOKEN }}@192.168.3.80/melabidi/alrahma_sunday_school_api.git . env: GIT_SSL_NO_VERIFY: "1" - name: Install system dependencies run: | apt-get update apt-get install -y --no-install-recommends \ git unzip curl libzip-dev libpng-dev libonig-dev libxml2-dev libsqlite3-dev \ nodejs docker-php-ext-install mbstring zip gd pdo_sqlite - name: Install Composer run: | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php --install-dir=/usr/local/bin --filename=composer rm composer-setup.php - name: Cache Composer dependencies uses: actions/cache@v4 with: path: | vendor/ .composer-cache/ key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} restore-keys: | ${{ runner.os }}-composer- - name: Install PHP dependencies run: | composer install --prefer-dist --no-interaction --no-progress composer dump-autoload - name: Composer validate run: composer validate - name: Laravel Pint (code style check) run: | composer require --dev laravel/pint --ignore-platform-reqs || true vendor/bin/pint --test || echo "Pint found style issues (non-blocking)" # ────────────────────────────────────────────── # TEST: PHPUnit (Unit + Feature with SQLite) # ────────────────────────────────────────────── test: name: Test (PHPUnit) runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 env: NODE_TLS_REJECT_UNAUTHORIZED: "0" steps: - name: Checkout repository run: | apt-get update apt-get install -y --no-install-recommends git ca-certificates git clone --depth 1 https://gitea:${{ secrets.GITHUB_TOKEN }}@192.168.3.80/melabidi/alrahma_sunday_school_api.git . env: GIT_SSL_NO_VERIFY: "1" - name: Install system dependencies run: | apt-get update apt-get install -y --no-install-recommends \ git unzip curl libzip-dev libpng-dev libonig-dev libxml2-dev libsqlite3-dev \ nodejs docker-php-ext-install mbstring zip gd pdo_sqlite - name: Install Composer run: | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php --install-dir=/usr/local/bin --filename=composer rm composer-setup.php - name: Cache Composer dependencies uses: actions/cache@v4 with: path: | vendor/ .composer-cache/ key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} restore-keys: | ${{ runner.os }}-composer- - name: Install PHP dependencies run: | composer install --prefer-dist --no-interaction --no-progress composer dump-autoload - name: Bootstrap Laravel testing environment run: | cat > .env <<'EOF' APP_NAME=school_api APP_ENV=testing APP_KEY= APP_DEBUG=false APP_URL=http://localhost LOG_CHANNEL=stderr LOG_LEVEL=debug DB_CONNECTION=sqlite DB_DATABASE=${{ github.workspace }}/database/database.sqlite CACHE_DRIVER=array SESSION_DRIVER=array QUEUE_CONNECTION=sync MAIL_MAILER=array FILESYSTEM_DISK=local JWT_SECRET=ci_test_jwt_secret_not_for_production EOF mkdir -p database storage/framework/cache storage/framework/sessions \ storage/framework/views storage/logs bootstrap/cache reports touch database/database.sqlite php artisan key:generate --force php artisan jwt:secret --force || true php artisan optimize:clear - name: Run database migrations run: php artisan migrate:fresh --force - name: Run Unit tests continue-on-error: true run: php artisan test --testsuite=Unit --log-junit reports/phpunit-unit.xml || echo "Unit tests have failures (non-blocking)" - name: Run Feature tests run: php artisan test --testsuite=Feature --log-junit reports/phpunit-feature.xml - name: Upload test reports uses: actions/upload-artifact@v3 if: always() continue-on-error: true with: name: test-reports path: reports/ retention-days: 7 # ────────────────────────────────────────────── # BUILD: Compile frontend assets (Vite) # ────────────────────────────────────────────── build: name: Build frontend assets runs-on: ubuntu-latest container: image: node:22-bookworm-slim env: NODE_TLS_REJECT_UNAUTHORIZED: "0" steps: - name: Checkout repository run: | apt-get update apt-get install -y --no-install-recommends git ca-certificates git clone --depth 1 https://gitea:${{ secrets.GITHUB_TOKEN }}@192.168.3.80/melabidi/alrahma_sunday_school_api.git . env: GIT_SSL_NO_VERIFY: "1" - name: Cache npm dependencies uses: actions/cache@v4 with: path: node_modules/ key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- - name: Install dependencies run: npm install --cache .npm --prefer-offline - name: Build assets run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v3 continue-on-error: true with: name: frontend-assets path: | public/build/ public/css/ public/js/ retention-days: 7 # ────────────────────────────────────────────── # SECURITY: Dependency audits + secret scanning # ────────────────────────────────────────────── security: name: Security audit runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 steps: - name: Checkout repository run: | apt-get update apt-get install -y --no-install-recommends git ca-certificates git clone --depth 1 https://gitea:${{ secrets.GITHUB_TOKEN }}@192.168.3.80/melabidi/alrahma_sunday_school_api.git . env: GIT_SSL_NO_VERIFY: "1" - name: Install system dependencies run: | apt-get update apt-get install -y --no-install-recommends git unzip curl libzip-dev libpng-dev libonig-dev libxml2-dev libsqlite3-dev nodejs docker-php-ext-install mbstring zip gd pdo_sqlite - name: Install Composer run: | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php --install-dir=/usr/local/bin --filename=composer rm composer-setup.php - name: Install PHP dependencies run: | composer install --prefer-dist --no-interaction --no-progress composer dump-autoload - name: Composer audit run: composer audit --locked --no-interaction --format=json || echo "Audit found advisories (non-blocking)" - name: Secret detection (basic) run: | echo "Scanning for committed secrets..." if grep -RInE \ "(APP_KEY=base64:[A-Za-z0-9+/=]{44}|DB_PASSWORD=.+|JWT_SECRET=.+|MAIL_PASSWORD=.+|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|OPENSSH|PRIVATE) KEY-----)" \ --exclude-dir=.git \ --exclude-dir=vendor \ --exclude-dir=node_modules \ --exclude=.env.example \ --exclude=.env.prod.example \ . 2>&1; then echo "❌ Secrets detected!" exit 1 else echo "✅ No secrets found" fi # ────────────────────────────────────────────── # DEPLOY (manual): to shared hosting via SSH # ────────────────────────────────────────────── deploy: name: Deploy to shared hosting (PHP) runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 env: NODE_TLS_REJECT_UNAUTHORIZED: "0" if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' needs: [test, build, security] environment: name: production url: ${{ vars.PRODUCTION_URL }} steps: - name: Checkout repository run: | apt-get update apt-get install -y --no-install-recommends git ca-certificates git clone --depth 1 https://gitea:${{ secrets.GITHUB_TOKEN }}@192.168.3.80/melabidi/alrahma_sunday_school_api.git . env: GIT_SSL_NO_VERIFY: "1" - name: Install system dependencies run: | apt-get update apt-get install -y --no-install-recommends \ git unzip curl libzip-dev libpng-dev libonig-dev libxml2-dev \ openssh-client sshpass rsync nodejs docker-php-ext-install mbstring zip gd pdo_mysql - name: Install Composer run: | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php --install-dir=/usr/local/bin --filename=composer rm composer-setup.php - name: Install production PHP dependencies run: | composer install --no-dev --optimize-autoloader --no-interaction --no-progress - name: Download frontend build artifacts uses: actions/download-artifact@v4 with: name: frontend-assets path: ./ - name: Deploy Laravel app to shared hosting run: | sshpass -p "${{ secrets.SSH_PASSWORD }}" rsync -avz --delete \ -e "ssh -p ${{ vars.SSH_PORT || 22 }} -o StrictHostKeyChecking=no" \ --exclude='.env' \ --exclude='.git' \ --exclude='.gitea' \ --exclude='tests' \ --exclude='storage/logs/*' \ --exclude='database/database.sqlite' \ --exclude='node_modules' \ --exclude='.npm' \ --exclude='reports' \ --exclude='.phpunit.result.cache' \ --exclude='package-lock.json' \ --exclude='.composer-cache' \ ./ \ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ vars.SSH_TARGET_DIR }}/ - name: Post-deploy artisan commands run: | sshpass -p "${{ secrets.SSH_PASSWORD }}" ssh \ -o StrictHostKeyChecking=no \ -p ${{ vars.SSH_PORT || 22 }} \ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \ "cd ${{ vars.SSH_TARGET_DIR }} && \ php artisan migrate --force && \ php artisan config:cache && \ php artisan route:cache && \ php artisan view:cache && \ php artisan optimize"