diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..3a09d2d3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + # Mise à jour automatique des actions GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Europe/Paris" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "chore(deps)" + + # Mise à jour automatique des paquets npm + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Europe/Paris" + labels: + - "dependencies" + - "npm" + commit-message: + prefix: "chore(deps)" + open-pull-requests-limit: 5 + ignore: + # Ignorer les mises à jour majeures automatiques (trop risquées) + - dependency-name: "*" + update-types: ["version-update:semver-major"] diff --git a/.github/workflows/automation-suite.yml b/.github/workflows/automation-suite.yml deleted file mode 100644 index bc96353f..00000000 --- a/.github/workflows/automation-suite.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Automation Suite - Dependencies & Optimization - -on: - schedule: - # Mise a jour des dependances chaque lundi a 9h (UTC) - - cron: '0 9 * * 1' - push: - branches: [gh-pages] - paths: - - '**.png' - - '**.jpg' - - '**.jpeg' - workflow_dispatch: - -permissions: - contents: write - issues: write - pull-requests: write - -jobs: - update-dependencies: - name: Mise a jour des dependances npm - runs-on: ubuntu-latest - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Update dependencies - run: | - npm update - npm audit fix || true - - - name: Get current date - id: get_date - run: echo "date=$(date +%Y-%m-%d)" >> "$GITHUB_OUTPUT" - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 - with: - add-paths: | - package.json - package-lock.json - commit-message: 'chore: mise a jour automatique des dependances npm' - title: 'Mise a jour automatique des dependances' - body: | - Mise a jour automatique des dependances npm effectuee le ${{ steps.get_date.outputs.date }} - - - Dependances mises a jour via npm update - - Corrections de securite appliquees via npm audit fix - - Merci de verifier les changements avant de fusionner. - branch: auto-update-dependencies - delete-branch: true - - optimize-images: - name: Optimisation des images - runs-on: ubuntu-latest - if: github.event_name == 'push' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Compress Images - uses: calibreapp/image-actions@f32575787d333b0579f0b7d506ff03be63a669d1 # 1.4.1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - compressOnly: true - jpegQuality: '85' - pngQuality: '85' - webpQuality: '85' - - check-links: - name: Verification des liens morts - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Check for broken links - id: lychee - uses: lycheeverse/lychee-action@v2 - with: - args: --verbose --no-progress --config .lychee.toml '**/*.html' '**/*.md' - fail: false - - - name: Create issue if broken links found - if: steps.lychee.outputs.exit_code != 0 - uses: peter-evans/create-issue-from-file@v5 - with: - title: 'Liens morts detectes sur le site' - content-filepath: ./lychee/out.md - labels: bug, maintenance diff --git a/.github/workflows/backup.yml b/.github/workflows/backup.yml deleted file mode 100644 index 2ef20ccc..00000000 --- a/.github/workflows/backup.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Weekly Backup & Archive - -on: - schedule: - - cron: '0 0 * * 0' - workflow_dispatch: - -permissions: - contents: write - -jobs: - create-backup: - name: Create Weekly Backup - runs-on: ubuntu-latest - - steps: - - name: Checkout gh-pages - uses: actions/checkout@v4 - with: - ref: gh-pages - fetch-depth: 0 - - - name: Generate backup - run: | - DATE=$(date +%Y-%m-%d_%H-%M-%S) - BACKUP_DIR="backup-$DATE" - mkdir -p "$BACKUP_DIR" - - # Copie du site en excluant .git et anciens backups - rsync -av --exclude='.git' --exclude='backup-*' ./ "$BACKUP_DIR/" - - # Archive compressee - tar -czf "$BACKUP_DIR.tar.gz" "$BACKUP_DIR" - - echo "Backup created: $BACKUP_DIR.tar.gz" - ls -lh "$BACKUP_DIR.tar.gz" - - - name: Create GitHub Release with backup - uses: softprops/action-gh-release@v2 - with: - tag_name: backup-${{ github.run_number }} - name: Weekly Backup - ${{ github.run_number }} - body: | - 🗄️ Automatic Weekly Backup - - Created: ${{ github.run_id }} - Trigger: Scheduled (every Sunday) - - This backup contains a complete snapshot of the website. - files: backup-*.tar.gz - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 7f1bd6de..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,103 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL Advanced" - -on: - push: - branches: [ "gh-pages" ] - pull_request: - branches: [ "gh-pages" ] - schedule: - - cron: '44 17 * * 1' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: javascript-typescript - build-mode: none - - language: ruby - build-mode: none - # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Add any setup steps before running the `github/codeql-action/init` action. - # This includes steps like installing compilers or runtimes (`actions/setup-node` - # or others). This is typically only required for manual builds. - # - name: Setup runtime (example) - # uses: actions/setup-example@v1 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml new file mode 100644 index 00000000..0a896f9c --- /dev/null +++ b/.github/workflows/images.yml @@ -0,0 +1,67 @@ +name: 🖼️ Optimisation des images + +on: + push: + branches: [main, gh-pages] + # Déclencher uniquement quand des images sont modifiées / ajoutées + paths: + - '**/*.jpg' + - '**/*.jpeg' + - '**/*.png' + - '**/*.webp' + - 'images/**' + - 'assets/images/**' + # Déclenchement manuel + workflow_dispatch: + +# Permissions minimales : écriture du contenu pour commiter les images optimisées +permissions: + contents: write + +jobs: + optimize-images: + name: Compression et optimisation des images + runs-on: ubuntu-latest + # Ne pas re-déclencher sur les commits automatiques du bot + if: github.actor != 'github-actions[bot]' + + steps: + # Récupérer le code source avec les nouvelles images + - name: Checkout du dépôt + uses: actions/checkout@v4 + + # Optimiser automatiquement les images JPEG, PNG et WebP + # Qualité 85% : bon compromis entre poids et fidélité visuelle + # La version est épinglée sur le commit de la version 1.4.1 pour la sécurité + - name: Optimisation des images (calibreapp/image-actions) + id: optimize + uses: calibreapp/image-actions@f32575787d333b0579f0b7d506ff03be63a669d1 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + # Ne pas créer de PR automatique : commiter directement + compressOnly: true + jpegQuality: '85' + pngQuality: '85' + webpQuality: '85' + + # Afficher un résumé des optimisations effectuées + - name: Résumé de l'optimisation + if: steps.optimize.outputs.markdown != '' + run: | + echo "## 🖼️ Résumé de l'optimisation des images" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "${{ steps.optimize.outputs.markdown }}" >> "$GITHUB_STEP_SUMMARY" + + # Commiter les images optimisées si des changements ont été effectués + - name: Commiter les images optimisées + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + if git diff --staged --quiet; then + echo "✅ Toutes les images sont déjà optimisées, aucun commit nécessaire." + else + git commit -m "chore(images): optimisation automatique des images [skip ci]" + git push + echo "✅ Images optimisées et commitées avec succès." + fi diff --git a/.github/workflows/lighthouse-audit.yml b/.github/workflows/lighthouse-audit.yml deleted file mode 100644 index 143b9ccc..00000000 --- a/.github/workflows/lighthouse-audit.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Lighthouse Performance & SEO Audit - -on: - push: - branches: [gh-pages] - schedule: - # Audit automatique chaque jour à 2h du matin (UTC) - - cron: '0 2 * * *' - workflow_dispatch: - -permissions: - contents: write - issues: write - pull-requests: write - -jobs: - lighthouse-audit: - name: Audit Lighthouse de davidkrk.com - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: gh-pages - - - name: Attendre le déploiement GitHub Pages - run: sleep 180 - - - name: Audit Lighthouse - uses: treosh/lighthouse-ci-action@12.6.2 - with: - urls: | - https://www.davidkrk.com - https://www.davidkrk.com/music.html - https://www.davidkrk.com/bio.html - https://www.davidkrk.com/contact.html - uploadArtifacts: true - temporaryPublicStorage: true - - - name: Créer badge de performance - run: | - mkdir -p .github/badges - echo '{"schemaVersion": 1, "label": "Lighthouse", "message": "Performance", "color": "brightgreen"}' > .github/badges/lighthouse.json - - - name: Commit du badge - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add .github/badges/lighthouse.json || true - git commit -m "chore: mise à jour du badge Lighthouse" || true - git push || true - - - name: Créer issue si score faible - if: failure() - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: '⚠️ Score Lighthouse faible détecté', - body: `Le score Lighthouse est tombé en dessous du seuil acceptable.\n\nVérifiez les résultats de l'audit dans les artifacts de ce workflow.`, - labels: ['performance', 'automated'] - }) diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 00000000..c6de9944 --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,123 @@ +name: 🔦 Lighthouse CI - Performance & SEO + +on: + # Audit sur chaque Pull Request ciblant la branche principale + pull_request: + branches: [main, gh-pages] + # Audit quotidien à 2h du matin (UTC) pour surveillance continue + schedule: + - cron: '0 2 * * *' + # Déclenchement manuel depuis l'onglet Actions + workflow_dispatch: + +# Permissions minimales pour ce workflow +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + lighthouse: + name: Audit Lighthouse - davidkrk.com + runs-on: ubuntu-latest + + steps: + # Récupérer le code source (nécessaire pour lire la config .lighthouserc.json) + - name: Checkout du dépôt + uses: actions/checkout@v4 + + # Lancer l'audit Lighthouse sur les pages principales du site + # Les seuils sont définis dans .lighthouserc.json : + # - Performance : minimum 70/100 + # - SEO : minimum 90/100 + # Le workflow échoue uniquement si les seuils bloquants définis dans + # .lighthouserc.json ne sont pas atteints (par ex. Performance/SEO). + # Les assertions configurées en `warn` n'échouent pas le job. + - name: Audit Lighthouse CI + id: lighthouse + uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 # 12.6.2 + with: + urls: | + https://www.davidkrk.com/ + https://www.davidkrk.com/music.html + https://www.davidkrk.com/bio.html + https://www.davidkrk.com/contact.html + configPath: .lighthouserc.json + # Sauvegarder les rapports en artefacts téléchargeables + uploadArtifacts: true + # Publier temporairement les rapports pour accès immédiat (lien dans le log) + temporaryPublicStorage: true + + # Sauvegarder les résultats JSON pour analyse ou tableau de bord + - name: Sauvegarder les résultats en artefact + if: always() + uses: actions/upload-artifact@v4 + with: + name: lighthouse-results-${{ github.run_id }} + path: '.lighthouseci/' + retention-days: 30 + + # Créer une issue GitHub si les scores sont inférieurs aux seuils + - name: Créer une issue si les scores sont insuffisants + if: failure() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + uses: actions/github-script@v7 + with: + script: | + // Créer les labels requis s'ils n'existent pas encore + const requiredLabels = [ + { name: 'performance', color: 'e4e669', description: 'Scores de performance' }, + { name: 'automated', color: 'ededed', description: 'Créée automatiquement par un workflow' } + ]; + for (const label of requiredLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name + }); + } catch (err) { + if (err.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + } else { + throw err; + } + } + } + + // Chercher si une issue de performance est déjà ouverte + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'performance', + state: 'open' + }); + + // Ne créer qu'une seule issue à la fois + if (issues.data.length === 0) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '⚠️ Score Lighthouse sous le seuil acceptable', + body: [ + '## 🔦 Alerte Performance / SEO', + '', + 'L\'audit Lighthouse automatique a détecté des scores inférieurs aux seuils définis :', + '- **Performance** : minimum requis **70/100**', + '- **SEO** : minimum requis **90/100**', + '', + `**Workflow déclenché** : ${context.workflow}`, + `**Run ID** : ${context.runId}`, + '', + '📊 Consultez les artefacts du workflow pour les rapports détaillés.', + '', + '_Issue créée automatiquement par le workflow Lighthouse CI._' + ].join('\n'), + labels: ['performance', 'automated'] + }); + } diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml new file mode 100644 index 00000000..9955ccc4 --- /dev/null +++ b/.github/workflows/maintenance.yml @@ -0,0 +1,218 @@ +name: 🛠️ Maintenance - Liens, Backup & Stale Issues + +on: + schedule: + # Vérification des liens morts chaque lundi à 8h UTC + - cron: '0 8 * * 1' + # Backup complet chaque dimanche à 1h UTC + - cron: '0 1 * * 0' + # Déclenchement manuel du workflow + workflow_dispatch: + +# Permissions minimales pour les opérations de maintenance +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + # ───────────────────────────────────────────────────────────── + # LUNDI : Vérification des liens morts avec Lychee + # ───────────────────────────────────────────────────────────── + check-links: + name: 🔗 Vérification des liens morts (Lychee) + runs-on: ubuntu-latest + # Exécuter uniquement le cron du lundi, ou lors d'un déclenchement manuel + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && github.event.schedule == '0 8 * * 1') + + steps: + # Récupérer le contenu du site publié (branche gh-pages) pour scanner ses liens + - name: Checkout du dépôt + uses: actions/checkout@v4 + with: + ref: gh-pages + + # Lancer Lychee pour vérifier tous les liens dans les fichiers HTML et Markdown + # La configuration est définie dans .lychee.toml (exclusions : mailto, soundcloud, etc.) + - name: Vérification des liens avec Lychee + id: lychee + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 + with: + args: > + --verbose + --no-progress + --config .lychee.toml + --timeout 30 + --max-retries 3 + '**/*.html' + '**/*.md' + # Ne pas faire échouer le workflow (on crée une issue à la place) + fail: false + + # S'assurer que les labels requis existent avant de créer l'issue + - name: Créer les labels si absents + if: steps.lychee.outputs.exit_code != 0 + uses: actions/github-script@v7 + with: + script: | + const requiredLabels = [ + { name: 'bug', color: 'd73a4a', description: 'Un problème a été identifié' }, + { name: 'maintenance', color: 'e4e669', description: 'Tâche de maintenance' }, + { name: 'automated', color: 'ededed', description: 'Créée automatiquement par un workflow' } + ]; + for (const label of requiredLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name + }); + } catch (err) { + if (err.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + } else { + throw err; + } + } + } + + # Créer une issue si des liens morts ont été trouvés + - name: Créer une issue si des liens morts sont détectés + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # v5 + with: + title: '🔗 Liens morts détectés sur davidkrk.com' + content-filepath: ./lychee/out.md + labels: bug, maintenance, automated + + # Sauvegarder le rapport complet en artefact + - name: Sauvegarder le rapport Lychee + if: always() + uses: actions/upload-artifact@v4 + with: + name: lychee-report-${{ github.run_id }} + path: lychee/ + retention-days: 14 + + # ───────────────────────────────────────────────────────────── + # DIMANCHE : Backup complet dans GitHub Releases + # ───────────────────────────────────────────────────────────── + backup: + name: 💾 Backup hebdomadaire (GitHub Releases) + runs-on: ubuntu-latest + # Exécuter uniquement le cron du dimanche ou un déclenchement manuel + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && github.event.schedule == '0 1 * * 0') + permissions: + contents: write + + steps: + # Récupérer le contenu du site publié (branche gh-pages) pour l'archiver + - name: Checkout complet du dépôt + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + + # Créer une archive compressée du site (en excluant .git et node_modules) + - name: Générer l'archive de backup + id: archive + run: | + DATE=$(date +%Y-%m-%d) + ARCHIVE_NAME="davidkrk-backup-${DATE}.tar.gz" + + # Créer l'archive en excluant les dossiers inutiles + tar -czf "$ARCHIVE_NAME" \ + --exclude='.git' \ + --exclude='node_modules' \ + --exclude='dist' \ + --exclude='*.tar.gz' \ + . + + ARCHIVE_SIZE=$(du -sh "$ARCHIVE_NAME" | cut -f1) + echo "archive_name=$ARCHIVE_NAME" >> "$GITHUB_OUTPUT" + echo "archive_size=$ARCHIVE_SIZE" >> "$GITHUB_OUTPUT" + echo "date=$DATE" >> "$GITHUB_OUTPUT" + echo "✅ Archive créée : $ARCHIVE_NAME ($ARCHIVE_SIZE)" + + # Publier l'archive dans une GitHub Release taguée + - name: Publier le backup dans GitHub Releases + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + with: + tag_name: backup-${{ steps.archive.outputs.date }} + name: "💾 Backup hebdomadaire — ${{ steps.archive.outputs.date }}" + body: | + ## 🗄️ Backup automatique hebdomadaire + + | Champ | Valeur | + |-------|--------| + | 📅 Date | ${{ steps.archive.outputs.date }} | + | 📦 Taille | ${{ steps.archive.outputs.archive_size }} | + | 🔁 Run | #${{ github.run_number }} | + | 🌿 Branche | `${{ github.ref_name }}` | + + Ce backup contient un instantané complet du site davidkrk.com. + Les backups sont conservés automatiquement pour permettre une restauration rapide. + files: ${{ steps.archive.outputs.archive_name }} + draft: false + prerelease: false + make_latest: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ───────────────────────────────────────────────────────────── + # LUNDI + DIMANCHE : Fermeture automatique des issues inactives (Stale) + # ───────────────────────────────────────────────────────────── + stale: + name: 🧹 Fermeture des issues inactives (Stale) + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + # Marquer les issues sans activité depuis 60 jours comme "stale" + # et les fermer automatiquement après 7 jours supplémentaires + - name: Gérer les issues et PR inactives + uses: actions/stale@v9 + with: + # ── Issues ────────────────────────────────────────── + # Marquer comme stale après 60 jours d'inactivité + days-before-issue-stale: 60 + # Fermer 7 jours après avoir été marquée stale + days-before-issue-close: 7 + stale-issue-label: 'stale' + stale-issue-message: | + 👋 Cette issue n'a pas eu d'activité depuis **60 jours**. + Elle sera automatiquement fermée dans **7 jours** si aucune réponse n'est apportée. + + Si le sujet est toujours d'actualité, merci de laisser un commentaire pour maintenir l'issue ouverte. + close-issue-message: | + 🔒 Cette issue a été fermée automatiquement après **67 jours** d'inactivité. + Elle peut être réouverte si nécessaire. + + # ── Pull Requests ──────────────────────────────────── + # Marquer les PR comme stale après 30 jours d'inactivité + days-before-pr-stale: 30 + days-before-pr-close: 7 + stale-pr-label: 'stale' + stale-pr-message: | + 👋 Cette Pull Request n'a pas eu d'activité depuis **30 jours**. + Elle sera automatiquement fermée dans **7 jours** si aucune mise à jour n'est effectuée. + close-pr-message: | + 🔒 Cette Pull Request a été fermée automatiquement après **37 jours** d'inactivité. + Elle peut être réouverte si nécessaire. + + # ── Exclusions ─────────────────────────────────────── + # Ne jamais fermer automatiquement les issues/PR avec ces labels + exempt-issue-labels: 'pinned,security,urgent,uptime,in-progress' + exempt-pr-labels: 'pinned,security,do-not-merge,in-progress' + # Limiter le nombre d'opérations par exécution + operations-per-run: 30 + # Supprimer le label stale si l'issue redevient active + remove-stale-when-updated: true diff --git a/.github/workflows/music-social.yml b/.github/workflows/music-social.yml new file mode 100644 index 00000000..37a3574b --- /dev/null +++ b/.github/workflows/music-social.yml @@ -0,0 +1,189 @@ +name: 🎵 Mise à jour musicale - Notification & Réseaux Sociaux + +on: + push: + branches: [main, gh-pages] + # Déclencher uniquement quand music.html est modifié + paths: + - 'music.html' + # Déclenchement manuel pour tester le workflow + workflow_dispatch: + +# Permissions minimales : lecture du code + création d'issues +permissions: + contents: read + issues: write + +jobs: + detect-music-update: + name: Détecter les changements dans music.html + runs-on: ubuntu-latest + + steps: + # Récupérer le code source avec l'historique complet pour comparer tout le push + - name: Checkout du dépôt + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Extraire les changements apportés à music.html depuis le dernier commit + - name: Analyser les modifications de music.html + id: diff + run: | + # Sur un push, comparer la plage exacte de commits de l'événement. + # En exécution manuelle (workflow_dispatch), revenir au commit précédent. + if [ "${{ github.event_name }}" = "push" ] && \ + [ -n "${{ github.event.before }}" ] && \ + [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then + DIFF=$(git diff "${{ github.event.before }}" "${{ github.sha }}" -- music.html) + elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then + DIFF=$(git diff HEAD^ HEAD -- music.html) + else + EMPTY_TREE=$(git hash-object -t tree /dev/null) + DIFF=$(git diff "$EMPTY_TREE" HEAD -- music.html) + fi + + # Extraire les lignes ajoutées (nouvelles entrées) + ADDED=$(echo "$DIFF" | grep "^+" | grep -v "^+++" | sed 's/^+//' | head -50) + + # Compter les lignes modifiées en excluant les en-têtes de diff (+++ / ---) + LINES_ADDED=$(echo "$DIFF" | grep "^+" | grep -v "^+++" | wc -l) + LINES_REMOVED=$(echo "$DIFF" | grep "^-" | grep -v "^---" | wc -l) + + # Sauvegarder les infos dans les outputs + echo "lines_added=$LINES_ADDED" >> "$GITHUB_OUTPUT" + echo "lines_removed=$LINES_REMOVED" >> "$GITHUB_OUTPUT" + + # Sauvegarder le diff dans un fichier temporaire pour l'issue + echo "$ADDED" > /tmp/music_changes.txt + + # Détecter la présence de nouveaux titres (balises audio ou liens) + if echo "$DIFF" | grep -qiE '(audio|track|mix|set|release|soundcloud|youtube)'; then + echo "has_music_content=true" >> "$GITHUB_OUTPUT" + else + echo "has_music_content=false" >> "$GITHUB_OUTPUT" + fi + + # Créer une issue GitHub récapitulative de la mise à jour musicale + - name: Créer une issue "New Music Update" + uses: actions/github-script@v7 + env: + LINES_ADDED: ${{ steps.diff.outputs.lines_added }} + LINES_REMOVED: ${{ steps.diff.outputs.lines_removed }} + COMMIT_SHA: ${{ github.sha }} + COMMIT_MSG: ${{ github.event.head_commit.message }} + COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }} + with: + script: | + const sha = process.env.COMMIT_SHA || ''; + const shortSha = sha.slice(0, 7); + const commitMsg = process.env.COMMIT_MSG || 'Mise à jour manuelle'; + const author = process.env.COMMIT_AUTHOR || 'DavidKRK'; + const linesAdded = process.env.LINES_ADDED; + const linesRemoved = process.env.LINES_REMOVED; + const now = new Date().toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); + + // ───────────────────────────────────────── + // Template de post pour réseaux sociaux + // ───────────────────────────────────────── + const socialTemplate = [ + '## 📱 Template de post pour réseaux sociaux', + '', + '### Instagram / Facebook', + '```', + '🎵 Nouvelle mise à jour sur mon site !', + '', + '🔥 Nouveau contenu musical disponible sur www.davidkrk.com', + '', + '🎧 DJ & Producteur Techno depuis 1999', + '📍 Saint-Jean-de-Luz, France', + '', + '#DavidKRK #Techno #DJ #Music #NewRelease #TechnoMusic #Underground', + '#BasqueCountry #SaintJeanDeLuz #ElectronicMusic', + '```', + '', + '### Twitter / X', + '```', + '🎵 Nouveau contenu musical sur https://davidkrk.com !', + '#DavidKRK #Techno #DJ #NewMusic', + '```', + '', + '### SoundCloud / Bandcamp', + '```', + '🔥 New track/mix available! Check it out on www.davidkrk.com', + '#techno #underground #dj #producer #saintjeandeLuz', + '```', + ].join('\n'); + + // ───────────────────────────────────────── + // Contenu principal de l'issue + // ───────────────────────────────────────── + const issueBody = [ + '## 🎵 Mise à jour de la page musicale détectée', + '', + '### 📋 Détails du commit', + `- **Commit** : [\`${shortSha}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.payload.head_commit?.id || sha})`, + `- **Message** : ${commitMsg}`, + `- **Auteur** : ${author}`, + `- **Date** : ${now} (heure de Paris)`, + `- **Lignes modifiées** : +${linesAdded} / -${linesRemoved}`, + '', + '### 🔗 Liens utiles', + `- 🌐 [Voir music.html en ligne](https://www.davidkrk.com/music.html)`, + `- 📝 [Voir le diff sur GitHub](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.payload.head_commit?.id || sha})`, + '', + '---', + '', + socialTemplate, + '', + '---', + '', + '### ✅ Checklist de publication', + '- [ ] Vérifier que le nouveau contenu est visible sur [davidkrk.com/music.html](https://www.davidkrk.com/music.html)', + '- [ ] Poster sur Instagram', + '- [ ] Poster sur Facebook', + '- [ ] Poster sur Twitter / X', + '- [ ] Partager sur SoundCloud si nouveau mix/track', + '- [ ] Mettre à jour la bio si nécessaire', + '', + '_Issue créée automatiquement par le workflow music-social._' + ].join('\n'); + + // S'assurer que les labels requis existent avant de créer l'issue + const requiredLabels = [ + { name: 'music', color: '1d76db', description: 'Mise à jour musicale' }, + { name: 'social-media', color: 'bfd4f2', description: 'Post réseaux sociaux' }, + { name: 'automated', color: 'ededed', description: 'Créée automatiquement par un workflow' } + ]; + for (const label of requiredLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name + }); + } catch (err) { + if (err.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + } else { + throw err; + } + } + } + + // Créer l'issue de notification + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `🎵 New Music Update — ${now}`, + body: issueBody, + labels: requiredLabels.map(l => l.name) + }); + + console.log('✅ Issue "New Music Update" créée avec succès !'); diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..8d0c998b --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,82 @@ +name: 🔒 Sécurité - CodeQL & Gitleaks + +on: + push: + branches: [main] + pull_request: + branches: [main, gh-pages] + schedule: + # Analyse de sécurité hebdomadaire chaque lundi à 3h UTC + - cron: '0 3 * * 1' + +# Permissions minimales : lecture du code + écriture des événements de sécurité +permissions: + contents: read + security-events: write + packages: read + actions: read + +jobs: + # ───────────────────────────────────────────────────────────── + # Analyse statique du code avec CodeQL + # ───────────────────────────────────────────────────────────── + codeql: + name: Analyse CodeQL (${{ matrix.language }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Analyse JavaScript / TypeScript (code front-end) + - language: javascript-typescript + build-mode: none + # Analyse des workflows GitHub Actions + - language: actions + build-mode: none + + steps: + # Récupérer le code source du dépôt + - name: Checkout du dépôt + uses: actions/checkout@v4 + + # Initialiser les outils CodeQL avec les requêtes de sécurité étendues + - name: Initialisation de CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + + # Exécuter l'analyse de sécurité et publier les résultats dans l'onglet Security + - name: Analyse CodeQL + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" + + # ───────────────────────────────────────────────────────────── + # Détection de secrets exposés avec Gitleaks + # ───────────────────────────────────────────────────────────── + gitleaks: + name: Détection de secrets (Gitleaks) + runs-on: ubuntu-latest + # Permissions minimales pour ce job + permissions: + contents: read + # Accès en lecture aux pull requests ; aucun commentaire automatique n'est publié + pull-requests: read + + steps: + # Récupérer l'historique GIT complet pour analyser tous les commits + - name: Checkout complet du dépôt + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Scanner l'intégralité du dépôt à la recherche de secrets exposés + # (clés API, mots de passe, tokens, etc.) + - name: Scan Gitleaks pour les secrets + uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Désactiver les commentaires automatiques sur les PR (bruit) + GITLEAKS_ENABLE_COMMENTS: false diff --git a/.github/workflows/uptime.yml b/.github/workflows/uptime.yml new file mode 100644 index 00000000..b7d556f1 --- /dev/null +++ b/.github/workflows/uptime.yml @@ -0,0 +1,188 @@ +name: 📡 Monitoring Uptime - davidkrk.com + +on: + schedule: + # Vérification de disponibilité toutes les 30 minutes + - cron: '*/30 * * * *' + # Déclenchement manuel pour un test immédiat + workflow_dispatch: + +# Permissions minimales : lecture du code + gestion des issues +permissions: + contents: write + issues: write + +jobs: + uptime-check: + name: Vérification de disponibilité + runs-on: ubuntu-latest + + steps: + # Récupérer explicitement la branche qui héberge les badges/statuts + - name: Checkout du dépôt + uses: actions/checkout@v4 + with: + ref: gh-pages + + # Tester la disponibilité du site via une requête HTTP + # Timeout de 30 secondes pour éviter les faux positifs réseau + - name: Vérifier la disponibilité de davidkrk.com + id: check + run: | + if ! HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + --max-time 30 \ + --retry 2 \ + --retry-delay 5 \ + "https://www.davidkrk.com/"); then + HTTP_STATUS="000" + fi + + if [ -z "$HTTP_STATUS" ]; then + HTTP_STATUS="000" + fi + echo "status_code=$HTTP_STATUS" >> "$GITHUB_OUTPUT" + + # Le site est considéré en ligne si le code HTTP est un entier entre 200 et 399 + if [[ "$HTTP_STATUS" =~ ^[0-9]+$ ]] && [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 400 ]; then + echo "is_up=true" >> "$GITHUB_OUTPUT" + echo "✅ Site en ligne (HTTP $HTTP_STATUS)" + else + echo "is_up=false" >> "$GITHUB_OUTPUT" + echo "❌ Site hors ligne ou erreur (HTTP $HTTP_STATUS)" + fi + + # Mettre à jour le fichier de statut pour le badge shields.io + - name: Mettre à jour le fichier de statut + run: | + mkdir -p .github/badges + + if [ "${{ steps.check.outputs.is_up }}" = "true" ]; then + STATUS="up" + COLOR="brightgreen" + MESSAGE="en ligne" + else + STATUS="down" + COLOR="red" + MESSAGE="hors ligne" + fi + + # Fichier JSON compatible avec shields.io endpoint + cat > .github/badges/uptime.json << EOF + { + "schemaVersion": 1, + "label": "davidkrk.com", + "message": "$MESSAGE", + "color": "$COLOR" + } + EOF + + # Commiter le fichier de statut si changement + - name: Commiter le statut de disponibilité + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add .github/badges/uptime.json || true + if git diff --staged --quiet; then + echo "Aucun changement de statut à commiter." + else + git commit -m "chore: mise à jour du statut uptime [skip ci]" + git push + fi + + # Si le site est de nouveau en ligne, fermer les issues d'alerte ouvertes + - name: Fermer l'alerte uptime si le site est revenu en ligne + if: steps.check.outputs.is_up == 'true' + uses: actions/github-script@v7 + with: + script: | + // Rechercher les issues d'alerte uptime ouvertes + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'uptime', + state: 'open' + }); + + // Fermer chaque issue et ajouter un commentaire de résolution + for (const issue of issues.data) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `✅ **Site de nouveau en ligne** — davidkrk.com répond normalement.\n\n_Résolu automatiquement le ${new Date().toLocaleString('fr-FR', { timeZone: 'Europe/Paris' })} (heure de Paris)._` + }); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed' + }); + } + + # Si le site est hors ligne, créer une issue d'alerte (une seule à la fois) + - name: Créer une alerte si le site est hors ligne + if: steps.check.outputs.is_up == 'false' + uses: actions/github-script@v7 + env: + STATUS_CODE: ${{ steps.check.outputs.status_code }} + with: + script: | + // Créer les labels requis s'ils n'existent pas encore dans le dépôt + const requiredLabels = [ + { name: 'uptime', color: '0075ca', description: 'Monitoring uptime du site' }, + { name: 'urgent', color: 'd93f0b', description: 'Nécessite une attention immédiate' } + ]; + for (const label of requiredLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name + }); + } catch (err) { + if (err.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description + }); + } else { + throw err; + } + } + } + + // Vérifier si une issue d'alerte uptime est déjà ouverte + const existing = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'uptime', + state: 'open' + }); + + // Ne créer qu'une seule alerte à la fois pour éviter le spam + if (existing.data.length === 0) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '🚨 Alerte : davidkrk.com est hors ligne', + body: [ + '## ⚠️ Le site davidkrk.com est inaccessible', + '', + `- **URL surveillée** : https://www.davidkrk.com/`, + `- **Code HTTP reçu** : \`${process.env.STATUS_CODE}\``, + `- **Détecté le** : ${new Date().toLocaleString('fr-FR', { timeZone: 'Europe/Paris' })} (heure de Paris)`, + '', + '### Actions recommandées', + '1. Vérifier l\'onglet **Déploiements** → GitHub Pages', + '2. Consulter les logs du workflow **Deploy**', + '3. Vérifier le DNS (CNAME → `davidkrk.github.io`)', + '', + '_Issue créée automatiquement par le workflow de monitoring uptime._', + '_Elle sera fermée automatiquement quand le site sera de nouveau accessible._' + ].join('\n'), + labels: ['uptime', 'urgent'] + }); + } diff --git a/.lighthouserc.json b/.lighthouserc.json new file mode 100644 index 00000000..8a94f80f --- /dev/null +++ b/.lighthouserc.json @@ -0,0 +1,34 @@ +{ + "ci": { + "assert": { + "preset": "lighthouse:no-pwa", + "assertions": { + "categories:performance": ["error", { "minScore": 0.7 }], + "categories:seo": ["error", { "minScore": 0.9 }], + "categories:accessibility": ["warn", { "minScore": 0.85 }], + "categories:best-practices": ["warn", { "minScore": 0.85 }], + "aria-command-name": "off", + "cls-culprits-insight": "off", + "color-contrast": "warn", + "errors-in-console": "warn", + "font-display": "warn", + "font-display-insight": "off", + "image-delivery-insight": "off", + "inspector-issues": "off", + "lcp-discovery-insight": "off", + "lcp-lazy-loaded": "off", + "network-dependency-tree-insight": "off", + "third-party-cookies": "off", + "total-byte-weight": "warn", + "unsized-images": "warn", + "unused-css-rules": "warn", + "unused-javascript": "warn", + "uses-optimized-images": "warn", + "uses-responsive-images": "warn" + } + }, + "collect": { + "numberOfRuns": 2 + } + } +} diff --git a/README.md b/README.md index 0e7fe9f3..f899f1c2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # 🎵 David KRK - Official Website [![Deploy Status](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/deploy.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/deploy.yml) -[![Lighthouse Audit](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse-audit.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse-audit.yml) -[![Automation Suite](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/automation-suite.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/automation-suite.yml) -[![Weekly Backup](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/backup.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/backup.yml) -[![Social Media](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/social-media-post.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/social-media-post.yml) +[![Security](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/security.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/security.yml) +[![Lighthouse CI](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse.yml) +[![Uptime Monitor](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/uptime.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/uptime.yml) +[![Images](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/images.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/images.yml) +[![Music Social](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/music-social.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/music-social.yml) +[![Maintenance](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/maintenance.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/maintenance.yml) ![Website](https://img.shields.io/website?url=https%3A%2F%2Fwww.davidkrk.com&label=davidkrk.com) ![GitHub last commit](https://img.shields.io/github/last-commit/DavidKRK/DavidKRK.github.io) @@ -67,10 +69,12 @@ This website is continuously monitored for: | Workflow | Purpose | Schedule | Status | |----------|---------|----------|--------| | **deploy.yml** | Main site deployment | On push | [![Deploy](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/deploy.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/deploy.yml) | -| **lighthouse-audit.yml** | Performance testing | Daily 3 AM | [![Lighthouse](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse-audit.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse-audit.yml) | -| **automation-suite.yml** | Maintenance & updates | Monday 10 AM | [![Automation](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/automation-suite.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/automation-suite.yml) | -| **backup.yml** | Weekly backups | Sunday 1 AM | [![Backup](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/backup.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/backup.yml) | -| **social-media-post.yml** | New music detection | On music.html change | [![Social](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/social-media-post.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/social-media-post.yml) | +| **security.yml** | CodeQL + Gitleaks security scan | On PR / push / Monday | [![Security](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/security.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/security.yml) | +| **lighthouse.yml** | Performance & SEO audit (fail < 80/90) | On PR / Daily 2 AM | [![Lighthouse CI](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/lighthouse.yml) | +| **uptime.yml** | HTTP uptime monitoring + issue alerts | Every 30 min | [![Uptime](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/uptime.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/uptime.yml) | +| **images.yml** | Auto image compression (JPEG/PNG/WebP) | On image push | [![Images](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/images.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/images.yml) | +| **music-social.yml** | New music detection + social post template | On music.html change | [![Music Social](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/music-social.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/music-social.yml) | +| **maintenance.yml** | Lychee link check + backup + stale issues | Mon 8 AM / Sun 1 AM | [![Maintenance](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/maintenance.yml/badge.svg)](https://github.com/DavidKRK/DavidKRK.github.io/actions/workflows/maintenance.yml) | ### 🚀 Getting Started