commit a51fd585a29b14aa27981210b200b2b08bd289b6 Author: Dominik Polakovics Date: Mon Feb 2 19:13:09 2026 +0100 initial commit for typo3 diff --git a/.forgejo/workflows/typo3-staged-deploy.yaml b/.forgejo/workflows/typo3-staged-deploy.yaml new file mode 100644 index 0000000..7da0481 --- /dev/null +++ b/.forgejo/workflows/typo3-staged-deploy.yaml @@ -0,0 +1,259 @@ +# Cloonar TYPO3 Staged Deployment Workflow +# +# Staged deployment: build → deploy to stage → run E2E tests → switch release +# +# Usage in project's .forgejo/workflows/deploy.yaml: +# name: Deploy +# on: +# push: +# branches: [main] +# jobs: +# deploy: +# uses: infrastructure/ci-templates/.forgejo/workflows/typo3-staged-deploy.yaml@main +# with: +# stage_url: https://myproject.cloonar.dev +# php_version: '8.3' +# secrets: +# stage_key: ${{ secrets.STAGE_KEY }} +# prod_key: ${{ secrets.PROD_KEY }} + +name: TYPO3 Staged Deploy + +on: + workflow_call: + inputs: + stage_url: + description: 'Staging URL for E2E tests' + required: true + type: string + php_version: + description: 'PHP version' + required: false + type: string + default: '8.3' + node_version: + description: 'Node.js version' + required: false + type: string + default: '20' + build_frontend: + description: 'Run npm build' + required: false + type: boolean + default: false + run_e2e_tests: + description: 'Run Playwright E2E tests' + required: false + type: boolean + default: true + e2e_path: + description: 'Path to E2E tests' + required: false + type: string + default: 'tests/e2e' + deployer_file: + description: 'Path to deploy.php' + required: false + type: string + default: './build/deploy.php' + deploy_production: + description: 'Also deploy to production after stage succeeds' + required: false + type: boolean + default: false + secrets: + stage_key: + description: 'SSH key for staging' + required: true + prod_key: + description: 'SSH key for production' + required: false + +env: + COMPOSER_ALLOW_SUPERUSER: 1 + +jobs: + # =========================================================================== + # Build + # =========================================================================== + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + tools: composer + + - name: Setup Node.js + if: ${{ inputs.build_frontend }} + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + cache: 'npm' + + - name: Install PHP dependencies + run: composer install --prefer-dist --no-progress --no-dev --ignore-platform-reqs + + - name: Install Node dependencies & build + if: ${{ inputs.build_frontend }} + run: | + npm ci + npm run build + + - name: Create artifact + run: | + tar -czf build.tar.gz \ + bin public packages config vendor build composer.json composer.lock \ + $([ -d "node_modules" ] && echo "node_modules") \ + $([ -d "dist" ] && echo "dist") \ + 2>/dev/null || true + + - uses: actions/upload-artifact@v4 + with: + name: build-${{ github.sha }} + path: build.tar.gz + retention-days: 1 + + + # =========================================================================== + # Deploy to Stage (release:create only) + # =========================================================================== + deploy-stage: + name: Upload to Stage + runs-on: ubuntu-latest + needs: [build] + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + + - uses: actions/download-artifact@v4 + with: + name: build-${{ github.sha }} + + - run: tar xf build.tar.gz && rm build.tar.gz + + - run: apt-get update && apt-get install -y openssh-client rsync + + - name: Upload release + uses: deployphp/action@v1 + with: + deployer-binary: "./bin/dep" + dep: --file=${{ inputs.deployer_file }} release:create stage + private-key: ${{ secrets.stage_key }} + + + # =========================================================================== + # Switch Stage Release + # =========================================================================== + switch-stage: + name: Activate Stage Release + runs-on: ubuntu-latest + needs: [deploy-stage] + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + + - uses: actions/download-artifact@v4 + with: + name: build-${{ github.sha }} + + - run: tar xf build.tar.gz && rm build.tar.gz + + - run: apt-get update && apt-get install -y openssh-client rsync + + - name: Switch release + uses: deployphp/action@v1 + with: + deployer-binary: "./bin/dep" + dep: --file=${{ inputs.deployer_file }} release:switch stage + private-key: ${{ secrets.stage_key }} + + + # =========================================================================== + # E2E Tests with Playwright + # =========================================================================== + test-stage: + name: E2E Tests + runs-on: ubuntu-latest + needs: [switch-stage] + if: ${{ inputs.run_e2e_tests }} + container: + image: mcr.microsoft.com/playwright:v1.50.0-noble + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + working-directory: ${{ inputs.e2e_path }} + run: npm ci + + - name: Run smoke tests + working-directory: ${{ inputs.e2e_path }} + env: + TEST_URL: ${{ inputs.stage_url }} + run: npx playwright test smoke + + - name: Run visual regression tests + working-directory: ${{ inputs.e2e_path }} + env: + TEST_URL: ${{ inputs.stage_url }} + run: npx playwright test visual + continue-on-error: true + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: ${{ inputs.e2e_path }}/playwright-report/ + retention-days: 7 + + - name: Upload diff screenshots + if: failure() + uses: actions/upload-artifact@v4 + with: + name: visual-diff + path: ${{ inputs.e2e_path }}/test-results/ + retention-days: 7 + + + # =========================================================================== + # Deploy to Production + # =========================================================================== + deploy-production: + name: Deploy Production + runs-on: ubuntu-latest + needs: [test-stage] + if: ${{ inputs.deploy_production && (needs.test-stage.result == 'success' || needs.test-stage.result == 'skipped') }} + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + + - uses: actions/download-artifact@v4 + with: + name: build-${{ github.sha }} + + - run: tar xf build.tar.gz && rm build.tar.gz + + - run: apt-get update && apt-get install -y openssh-client rsync + + - name: Deploy to production + uses: deployphp/action@v1 + with: + deployer-binary: "./bin/dep" + dep: --file=${{ inputs.deployer_file }} release:create production + private-key: ${{ secrets.prod_key }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8940d0 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# Cloonar CI Templates + +Shared CI/CD templates for Cloonar projects. + +## Contents + +``` +.forgejo/workflows/ + typo3-deploy.yaml # Simple deployment workflow + typo3-staged-deploy.yaml # Staged deployment with E2E tests + +deployer/ + typo3-recipe.php # Shared Deployer configuration + +examples/ + project-deploy.php # Example project deploy.php + project-servers.yaml # Example servers.yaml + project-workflow-*.yaml # Example workflow files +``` + +## Quick Start + +### 1. Update your project's `build/deploy.php` + +Replace your entire deploy.php with: + +```php +set('cachetool', '/var/run/phpfpm/myproject.cloonar.dev.sock'); +host('production')->set('cachetool', '/var/run/phpfpm/myproject.at.sock'); +``` + +### 2. Keep your `build/servers.yaml` (project-specific) + +```yaml +hosts: + stage: + hostname: web-arm.cloonar.com + remote_user: myproject_cloonar_dev + deploy_path: ~/ + # ... rest of config +``` + +### 3. Update your workflow + +Replace `.forgejo/workflows/deploy.yaml` with: + +**Simple deployment:** +```yaml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + uses: infrastructure/ci-templates/.forgejo/workflows/typo3-deploy.yaml@main + with: + target: stage + php_version: '8.3' + secrets: + deploy_key: ${{ secrets.STAGE_KEY }} +``` + +**Staged deployment with tests:** +```yaml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + uses: infrastructure/ci-templates/.forgejo/workflows/typo3-staged-deploy.yaml@main + with: + stage_url: https://myproject.cloonar.dev + run_e2e_tests: true + secrets: + stage_key: ${{ secrets.STAGE_KEY }} + prod_key: ${{ secrets.PROD_KEY }} +``` + +## Workflow Options + +### typo3-deploy.yaml + +| Input | Default | Description | +|-------|---------|-------------| +| `target` | required | `stage` or `production` | +| `task` | `deploy` | Deployer task (`deploy`, `release:create`, `release:switch`) | +| `php_version` | `8.3` | PHP version | +| `run_tests` | `false` | Run PHPStan/Psalm before deploy | +| `build_frontend` | `false` | Run `npm ci && npm run build` | + +### typo3-staged-deploy.yaml + +| Input | Default | Description | +|-------|---------|-------------| +| `stage_url` | required | URL for E2E tests | +| `php_version` | `8.3` | PHP version | +| `run_e2e_tests` | `true` | Run Playwright tests after stage deploy | +| `e2e_path` | `tests/e2e` | Path to E2E test directory | +| `deploy_production` | `false` | Auto-deploy to prod after tests pass | + +## Deployer Tasks + +The shared recipe provides these tasks: + +- `release:create ` - Upload release without switching (for staged deploys) +- `release:switch ` - Switch to uploaded release +- `deploy ` - Full deploy (create + switch) + +## Migration Checklist + +- [ ] Create `infrastructure/ci-templates` repo with these files +- [ ] Update project's `build/deploy.php` to use shared recipe +- [ ] Update project's `.forgejo/workflows/` to use reusable workflows +- [ ] Delete old `.drone.yml` files +- [ ] Test on one project first (e.g., gbv-aktuell) diff --git a/deployer/typo3-recipe.php b/deployer/typo3-recipe.php new file mode 100644 index 0000000..4652ee3 --- /dev/null +++ b/deployer/typo3-recipe.php @@ -0,0 +1,192 @@ +set('cachetool', '/var/run/phpfpm/PROJECT.cloonar.dev.sock'); + * host('production')->set('cachetool', '/var/run/phpfpm/PROJECT.TLD.sock'); + */ + +namespace Deployer; + +require 'recipe/common.php'; +require 'contrib/rsync.php'; +require 'contrib/cachetool.php'; + +// ============================================================================= +// PHP Configuration +// ============================================================================= + +set('bin/php', function () { + return '/run/current-system/sw/bin/php'; +}); + +// ============================================================================= +// Shared Directories & Files +// ============================================================================= + +set('shared_dirs', [ + 'public/fileadmin', + 'var' +]); + +set('shared_files', [ + '.env', + 'config/system/additional.php', +]); + +set('writable_dirs', [ + 'public/typo3temp' +]); + +// ============================================================================= +// Rsync Configuration +// ============================================================================= + +set('rsync_src', '../'); + +set('rsync', [ + 'exclude' => [ + // Shared (will be symlinked) + 'public/fileadmin', + 'var', + '.env', + 'config/system/additional.php', + // Build & dev + '.composer-cache', + 'build', + '.git*', + '.ddev', + '.editorconfig', + '.idea', + 'tests', + 'node_modules', + // Config files + '.php-cs-fixer.php', + 'phpstan.neon', + 'phpstan-baseline.neon', + 'psalm.xml', + 'psalm-baseline.xml', + 'renovate.json', + '*.md', + ], + 'exclude-file' => false, + 'include' => [], + 'include-file' => false, + 'filter' => ['dir-merge,-n /.gitignore'], + 'filter-file' => false, + 'filter-perdir' => false, + 'flags' => 'avz', + 'options' => ['delete'], + 'timeout' => 300 +]); + +// Disable git-based code update (we use rsync) +task('deploy:update_code')->hidden()->disable(); + +// ============================================================================= +// TYPO3 Tasks +// ============================================================================= + +task('typo3:extension:setup', function () { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 extension:setup'); +})->desc('Run TYPO3 extension:setup'); + +task('typo3:database:updateschema', function() { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 database:updateschema'); +}); + +task('typo3:referenceindex:update', function() { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 referenceindex:update'); +}); + +task('typo3:install:fixfolderstructure', function() { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 install:fixfolderstructure'); +}); + +task('typo3:cache:flush', function () { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 cache:flush'); +})->desc('Flush TYPO3 caches'); + +task('typo3:cache:warmup', function () { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 cache:warmup'); +})->desc('Warmup TYPO3 caches'); + +task('typo3:language:update', function () { + cd('{{release_or_current_path}}'); + run('{{bin/php}} bin/typo3 language:update'); +})->desc('Update TYPO3 language files'); + + +// Helper to get correct path (release during deploy, current after switch) +set('release_or_current_path', function () { + return test('[ -d {{release_path}} ]') ? get('release_path') : 'current'; +}); + +// ============================================================================= +// PHP-FPM Reload (for NixOS infrastructure) +// ============================================================================= + +task('php:reload', function () { + run('php-reload'); +})->desc('Reload PHP-FPM'); + +// ============================================================================= +// Deployment Strategies +// ============================================================================= + +/** + * release:create - Upload new release without switching + * Use for staged deployments where you want to test before switching + */ +task('release:create', [ + 'deploy:prepare', + 'rsync', + 'deploy:vendors', + 'deploy:shared', + 'deploy:writable', + 'typo3:install:fixfolderstructure', // Safe, just creates folders + 'deploy:unlock', + 'deploy:success' +])->desc('Create new release (upload only, no switch)'); + +/** + * release:switch - Switch to latest release and cleanup + * Run after release:create once testing passes + */ +task('release:switch', [ + 'deploy:symlink', + 'cachetool:clear:opcache', + 'php:reload', + 'php:reload-prod', + 'typo3:extension:setup', // DB schema for extensions + 'typo3:database:updateschema', // Core DB migrations + 'typo3:cache:flush', + 'typo3:referenceindex:update', // Fix reference index + 'typo3:language:update', + 'typo3:cache:warmup', + 'deploy:unlock', + 'deploy:cleanup', + 'deploy:success' +])->desc('Switch to latest release'); + +// ============================================================================= +// Hooks +// ============================================================================= + +// Unlock on failure +after('deploy:failed', 'deploy:unlock'); diff --git a/examples/project-deploy.php b/examples/project-deploy.php new file mode 100644 index 0000000..79fe623 --- /dev/null +++ b/examples/project-deploy.php @@ -0,0 +1,29 @@ +set('cachetool', '/var/run/phpfpm/PROJECTNAME.cloonar.dev.sock'); + +host('production') + ->set('cachetool', '/var/run/phpfpm/PROJECTNAME.TLD.sock'); + +// Optional: Override settings for this project +// set('shared_dirs', array_merge(get('shared_dirs'), ['public/uploads'])); +// set('keep_releases', 10); diff --git a/examples/project-servers.yaml b/examples/project-servers.yaml new file mode 100644 index 0000000..d38c635 --- /dev/null +++ b/examples/project-servers.yaml @@ -0,0 +1,21 @@ +# Example project servers.yaml +# Rename PROJECTNAME to your actual project + +hosts: + stage: + stage: staging + hostname: web-arm.cloonar.com + remote_user: PROJECTNAME_cloonar_dev + writable_mode: chmod + forward_agent: true + deploy_path: ~/ + keep_releases: 1 + + production: + stage: production + hostname: web-arm.cloonar.com + remote_user: PROJECTNAME_TLD + writable_mode: chmod + forward_agent: true + deploy_path: ~/ + keep_releases: 5 diff --git a/examples/project-workflow-staged.yaml b/examples/project-workflow-staged.yaml new file mode 100644 index 0000000..845a5ea --- /dev/null +++ b/examples/project-workflow-staged.yaml @@ -0,0 +1,25 @@ +# Example: Staged deployment with E2E tests (like gbv-aktuell) +# Place in your project's .forgejo/workflows/deploy.yaml + +name: Build and Deploy + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'renovate.json' + workflow_dispatch: + +jobs: + deploy: + uses: infrastructure/ci-templates/.forgejo/workflows/typo3-staged-deploy.yaml@main + with: + stage_url: https://PROJECTNAME.cloonar.dev + php_version: '8.3' + run_e2e_tests: true + e2e_path: tests/e2e + deploy_production: false # Set true for auto-deploy to prod after tests pass + secrets: + stage_key: ${{ secrets.STAGE_KEY }} + prod_key: ${{ secrets.PROD_KEY }}