diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..277c069 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + + - package-ecosystem: composer + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + composer-minor-and-patch: + update-types: + - minor + - patch + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + npm-minor-and-patch: + update-types: + - minor + - patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0335421 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: CI + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + php: + name: PHP quality and tests + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + APP_ENV: testing + APP_KEY: base64:MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY= + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.3' + extensions: ctype, dom, fileinfo, iconv, mbstring, pdo_sqlite, sqlite3, xml + coverage: none + tools: composer:v2 + + - name: Validate Composer files + run: composer validate --no-check-publish + + - name: Install PHP dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Audit PHP dependencies + run: composer audit --locked --no-interaction + + - name: Check PHP formatting + run: composer lint + + - name: Run PHP tests + run: composer test + + frontend: + name: Frontend audit and build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '20.x' + cache: npm + + - name: Install frontend dependencies + run: npm ci + + - name: Audit frontend dependencies + run: npm audit --audit-level=high + + - name: Build frontend assets + run: npm run build + + dependency-review: + name: Dependency review + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Review dependency changes + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: high diff --git a/.github/workflows/deploy-validation.yml b/.github/workflows/deploy-validation.yml deleted file mode 100644 index 85802b8..0000000 --- a/.github/workflows/deploy-validation.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Deploy to Validation - -on: - workflow_dispatch: - -env: - PANDORA_ENABLED: ${{ secrets.VALIDATION_PANDORA_ENABLED }} - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Show - run: | - echo "BASTION_HOST=${{ vars.BASTION_HOST }}" - echo "BASTION_SSH_PORT=${{ vars.BASTION_SSH_PORT }}" - - name: Install dependencies and build - run: | - npm ci - npm run build - zip -r build_artifacts.zip public/build - - - name: Install SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.VALIDATION_ENV_SSH_PRIVATE_KEY }} - known_hosts: 'placeholder' - if_key_exists: replace - - - name: Adding known hosts on source host from bastion host - run: ssh-keyscan -p ${{ secrets.BASTION_SSH_PORT }} -H ${{ secrets.BASTION_HOST }} >> ~/.ssh/known_hosts - - - name: Adding known hosts on source host from target host - run: ssh -p ${{ secrets.BASTION_SSH_PORT }} ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }} ssh-keyscan -H ${{ secrets.VALIDATION_ENV_HOST }} >> ~/.ssh/known_hosts - - - - name: Disable StrictHostKeyChecking for CI - run: | - mkdir -p ~/.ssh - cat >> ~/.ssh/config << 'EOF' - Host * - StrictHostKeyChecking no - UserKnownHostsFile=/dev/null - EOF - - - name: Sync Files to Server - run: | - rsync -avz --delete --no-perms \ - -e "ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }}" \ - --exclude 'node_modules' \ - --exclude '.git' \ - --exclude '.github' \ - --exclude 'tests' \ - ./ ${{ secrets.SERVER_USERNAME }}@${{ secrets.VALIDATION_ENV_HOST }}:/var/www/test.applications.nc3.lu/ - - name: Create Configuration Files and Deploy - run: | - ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }} ${{ secrets.SERVER_USERNAME }}@${{ secrets.VALIDATION_ENV_HOST }} "bash -s" << 'ENDSSH' - - export http_proxy="${{ secrets.PROXY }}" - export https_proxy="${{ secrets.PROXY }}" - export HTTP_PROXY="${{ secrets.PROXY }}" - export HTTPS_PROXY="${{ secrets.PROXY }}" - cd /var/www/test.applications.nc3.lu - - - # Create .env file - cat > .env << 'EOL' - APP_NAME="NC3's Application Platform" - APP_ENV=production - APP_KEY=${{ secrets.APP_KEY_TEST }} - APP_DEBUG=true - APP_URL=https://test.applications.nc3.lu - APP_LOCALE=en - APP_FALLBACK_LOCALE=en - APP_FAKER_LOCALE=en_US - ASSET_URL=https://test.applications.nc3.lu - - APP_MAINTENANCE_DRIVER=file - BCRYPT_ROUNDS=12 - - LOG_CHANNEL=stack - LOG_STACK=single - LOG_DEPRECATIONS_CHANNEL=null - LOG_LEVEL=debug - - DB_CONNECTION=mysql - DB_HOST=db - DB_PORT=3306 - DB_DATABASE=${{ secrets.DB_DATABASE }} - DB_USERNAME=${{ secrets.DB_USERNAME }} - DB_PASSWORD=${{ secrets.DB_PASSWORD }} - - SESSION_DRIVER=database - SESSION_LIFETIME=120 - SESSION_ENCRYPT=false - SESSION_PATH=/ - SESSION_DOMAIN=null - SESSION_STORE= - - BROADCAST_CONNECTION=log - FILESYSTEM_DISK=local - QUEUE_CONNECTION=database - - CACHE_STORE=database - CACHE_PREFIX= - - MAIL_MAILER=smtp - MAIL_HOST=mail.mbox.lu - MAIL_PORT=587 - MAIL_USERNAME=${{ secrets.MAIL_USERNAME }} - MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} - MAIL_ENCRYPTION=tls - MAIL_FROM_ADDRESS=app@nc3.lu - MAIL_FROM_NAME="NC3's Application Platform" - - VITE_APP_NAME="NC3's Application Platform" - API_DOCS_ALLOWED_DOMAINS=lhc.lu,circl.lu,nc3.lu - PANDORA_ENABLED=true - PANDORA_URL=http://pandora:6100 - PANDORA_TIMEOUT=15 - PANDORA_BLOCK_MALICIOUS=true - EOL - chmod 644 .env - - # Create docker-compose.env file - cat > docker-compose.env << 'EOL' - DB_DATABASE=${{ secrets.DB_DATABASE }} - DB_USERNAME=${{ secrets.DB_USERNAME }} - DB_PASSWORD=${{ secrets.DB_PASSWORD }} - MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }} - PROXY=${{ secrets.PROXY }} - EOL - chmod 644 docker-compose.env - # Try pulling images explicitly first - docker pull composer:2 - docker pull node:20-alpine - docker pull php:8.3-fpm-alpine - # Extract build artifacts and deploy - unzip -o build_artifacts.zip - chmod +x scripts/deploy.sh - ./scripts/deploy.sh - ENDSSH - - - name: Optional Pandora installation - if: env.PANDORA_ENABLED == 'true' - run: | - ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }} \ - ${{ secrets.SERVER_USERNAME }}@${{ secrets.VALIDATION_ENV_HOST }} \ - "bash /var/www/test.applications.nc3.lu/scripts/setup-pandora.sh $PANDORA_ENABLED '${{ secrets.PROXY }}'" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index b009250..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Deploy to Production - -on: - push: - branches: [ master ] - workflow_dispatch: - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Install dependencies and build - run: | - npm ci - npm run build - zip -r build_artifacts.zip public/build - - - name: Install SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.PRODUCTION_ENV_SSH_PRIVATE_KEY }} - known_hosts: 'placeholder' - if_key_exists: replace - - - name: Adding known hosts on source host from bastion host - run: ssh-keyscan -p ${{ secrets.BASTION_SSH_PORT }} -H ${{ secrets.BASTION_HOST }} >> ~/.ssh/known_hosts - - - name: Adding known hosts on source host from target host - run: ssh -p ${{ secrets.BASTION_SSH_PORT }} ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }} ssh-keyscan -H ${{ secrets.PRODUCTION_ENV_HOST }} >> ~/.ssh/known_hosts - - - name: Disable StrictHostKeyChecking for CI - run: | - mkdir -p ~/.ssh - cat >> ~/.ssh/config << 'EOF' - Host * - StrictHostKeyChecking no - UserKnownHostsFile=/dev/null - EOF - - - name: Sync Files to Server - # public/storage holds uploaded header images and is gitignored, so it - # is absent from the source tree — without excluding it, --delete wipes - # every uploaded image on each deploy. - run: | - rsync -avz --delete --no-perms \ - -e "ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }}" \ - --exclude 'node_modules' \ - --exclude '.git' \ - --exclude '.github' \ - --exclude 'tests' \ - --exclude 'public/storage' \ - ./ ${{ secrets.SERVER_USERNAME }}@${{ secrets.PRODUCTION_ENV_HOST }}:/var/www/applications.nc3.lu/ - - name: Create Configuration Files and Deploy - env: - PANDORA_ENABLED: ${{ secrets.PRODUCTION_PANDORA_ENABLED }} - run: | - ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }} ${{ secrets.SERVER_USERNAME }}@${{ secrets.PRODUCTION_ENV_HOST }} "bash -s" << 'ENDSSH' - cd /var/www/applications.nc3.lu - - # Create .env file - # Quoted delimiter: every value here is substituted by GitHub Actions - # before the shell runs, so no expansion is needed. Leaving it - # unquoted lets a '$' or backtick inside a secret corrupt the value or - # command-substitute on the remote host. - cat > .env << 'EOL' - APP_NAME="NC3's Application Platform" - APP_ENV=production - APP_KEY=${{ secrets.APP_KEY }} - APP_DEBUG=false - APP_URL=https://applications.nc3.lu - APP_LOCALE=en - APP_FALLBACK_LOCALE=en - APP_FAKER_LOCALE=en_US - ASSET_URL=https://applications.nc3.lu - - APP_MAINTENANCE_DRIVER=file - BCRYPT_ROUNDS=12 - - LOG_CHANNEL=stack - LOG_STACK=single - LOG_DEPRECATIONS_CHANNEL=null - LOG_LEVEL=warning - - DB_CONNECTION=mysql - DB_HOST=db - DB_PORT=3306 - DB_DATABASE=${{ secrets.DB_DATABASE }} - DB_USERNAME=${{ secrets.DB_USERNAME }} - DB_PASSWORD=${{ secrets.DB_PASSWORD }} - - SESSION_DRIVER=database - SESSION_LIFETIME=120 - SESSION_ENCRYPT=true - SESSION_PATH=/ - SESSION_DOMAIN=null - SESSION_STORE= - SESSION_SECURE_COOKIE=true - - BROADCAST_CONNECTION=log - FILESYSTEM_DISK=local - QUEUE_CONNECTION=database - - CACHE_STORE=database - CACHE_PREFIX= - - MAIL_MAILER=smtp - MAIL_HOST=mail.mbox.lu - MAIL_PORT=587 - MAIL_USERNAME=${{ secrets.MAIL_USERNAME }} - MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} - MAIL_ENCRYPTION=tls - MAIL_FROM_ADDRESS=app@nc3.lu - MAIL_FROM_NAME="NC3's Application Platform" - - VITE_APP_NAME="NC3's Application Platform" - API_DOCS_ALLOWED_DOMAINS=lhc.lu,circl.lu,nc3.lu - CORS_ALLOWED_ORIGINS=https://applications.nc3.lu - PANDORA_ENABLED=${{ env.PANDORA_ENABLED }} - PANDORA_URL=http://pandora:6100 - PANDORA_TIMEOUT=15 - PANDORA_BLOCK_MALICIOUS=true - EOL - chmod 644 .env - - # Create docker-compose.env file - cat > docker-compose.env << 'EOL' - DB_DATABASE=${{ secrets.DB_DATABASE }} - DB_USERNAME=${{ secrets.DB_USERNAME }} - DB_PASSWORD=${{ secrets.DB_PASSWORD }} - MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }} - PROXY=${{ secrets.PROXY }} - EOL - chmod 644 docker-compose.env - - # Extract build artifacts and deploy - unzip -o build_artifacts.zip - chmod +x scripts/deploy.sh - ./scripts/deploy.sh - ENDSSH - - - name: Optional Pandora installation - if: env.PANDORA_ENABLED == 'true' - run: | - ssh -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }}:${{ secrets.BASTION_SSH_PORT }} \ - ${{ secrets.SERVER_USERNAME }}@${{ secrets.PRODUCTION_ENV_HOST }} \ - "bash /var/www/applications.nc3.lu/scripts/setup-pandora.sh $PANDORA_ENABLED '${{ secrets.PROXY }}'" diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2..388f6b5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,5 +6,10 @@ abstract class TestCase extends BaseTestCase { - // + protected function setUp(): void + { + parent::setUp(); + + $this->withoutVite(); + } } diff --git a/tests/Unit/DeploymentPermissionsTest.php b/tests/Unit/DeploymentPermissionsTest.php index 76da98a..3277211 100644 --- a/tests/Unit/DeploymentPermissionsTest.php +++ b/tests/Unit/DeploymentPermissionsTest.php @@ -25,17 +25,4 @@ public function test_web_root_guard_restores_only_missing_traversal_permission() rmdir($projectDirectory); } } - - public function test_deployment_sync_does_not_copy_checkout_permissions_to_server(): void - { - foreach (['deploy.yml', 'deploy-validation.yml'] as $workflow) { - $contents = file_get_contents(base_path('.github/workflows/'.$workflow)); - - $this->assertStringContainsString( - 'rsync -avz --delete --no-perms', - $contents, - "$workflow must preserve the destination directory permissions", - ); - } - } }