Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3899c65
wawp-updater : configuration et validation stricte des réponses du se…
wearewp-agence-wordpress Oct 12, 2026
20d4c04
wawp-updater : vérification Ed25519 des paquets
wearewp-agence-wordpress Oct 12, 2026
7321ada
wawp-updater : appels HTTP /v1/check et /v1/info
wearewp-agence-wordpress Oct 12, 2026
4945809
wawp-updater : points d'accroche WordPress, jeton frais et signature …
wearewp-agence-wordpress Oct 12, 2026
0bf13b9
wawp-updater : vidage du cache avant la vérification, détails jamais …
wearewp-agence-wordpress Oct 12, 2026
94043bc
wawp-updater : chargement partagé, la copie la plus récente gère tous…
wearewp-agence-wordpress Oct 12, 2026
4100bef
Génération des clés et signature des paquets
wearewp-agence-wordpress Oct 12, 2026
a2ce378
Contrôles d'un plugin avant publication
wearewp-agence-wordpress Oct 12, 2026
f324a68
Workflow réutilisable de publication : construction, signature et env…
wearewp-agence-wordpress Oct 12, 2026
c3a3e99
Script de préparation d'un dépôt de plugin
wearewp-agence-wordpress Oct 12, 2026
4c3c101
Validation bout en bout sur un vrai WordPress contre le serveur de mi…
wearewp-agence-wordpress Oct 12, 2026
1008d7e
E2E : preuve du jeton périmé et contrôles positifs
wearewp-agence-wordpress Oct 12, 2026
ef1d3ab
Intégration continue et procédure de mise en place
wearewp-agence-wordpress Oct 12, 2026
086b196
Procédure de mise en place : ordre de protection du tag et précisions
wearewp-agence-wordpress Oct 12, 2026
55ce92d
Client : aucune erreur fatale si un fichier manque ; classe Package d…
wearewp-agence-wordpress Oct 12, 2026
810bf1c
Client : refus d'un paquet signé non conforme à l'offre, entrée no_up…
wearewp-agence-wordpress Oct 12, 2026
515be39
Envoi FTPS : lftp s'arrête à la première erreur et nom du paquet vali…
wearewp-agence-wordpress Oct 12, 2026
f595ca7
Chaîne de publication : contrôle du paquet construit, version minimal…
wearewp-agence-wordpress Oct 12, 2026
a9f5aa4
setup-repo : liste des rulesets capturée avant le grep (pas de pipe s…
wearewp-agence-wordpress Oct 12, 2026
a341d9a
Procédure : rotation et version minimale, délai de 24 h, hôte FTPS, w…
wearewp-agence-wordpress Oct 12, 2026
7ae8a11
Client : refus du paquet détourné à l'intérieur du try/catch du filtre
wearewp-agence-wordpress Oct 12, 2026
528be0a
E2E : plugin à jour rangé dans no_update
wearewp-agence-wordpress Oct 12, 2026
c06e982
wawp-updater : clés publiques de signature (active et secours)
wearewp-agence-wordpress Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
push:
pull_request:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Extensions PHP
run: |
php -v
for ext in json sodium zip curl; do php -m | grep -qx "$ext" || { echo "Extension manquante : $ext"; exit 1; }; done

- name: Dépendances
run: composer install --no-interaction --prefer-dist --no-progress

- name: Tests unitaires
run: composer test

- name: Analyse statique
run: composer stan

- name: Standards WordPress et compatibilité PHP 7.4
run: composer cs
187 changes: 187 additions & 0 deletions .github/workflows/plugin-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
name: Publication d'un plugin

on:
workflow_call:
inputs:
tools-ref:
description: Version de release-tools utilisée pour les scripts (doit correspondre à la version du workflow).
type: string
default: v1

permissions:
contents: read

jobs:
build:
name: Contrôles et paquet
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
outputs:
slug: ${{ steps.meta.outputs.slug }}
version: ${{ steps.meta.outputs.version }}
steps:
- name: Code du plugin
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: plugin
persist-credentials: false

- name: Outils de publication
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: wearewp/release-tools
ref: ${{ inputs.tools-ref }}
path: tools
persist-credentials: false

# Un relèvement de la version minimale sur main s'applique même aux appels épinglés sur un ancien tag.
- name: Version minimale de wawp-updater exigée sur main
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: wearewp/release-tools
ref: main
path: tools-main
sparse-checkout: config
persist-credentials: false

- name: Slug et version
id: meta
run: |
echo "slug=${GITHUB_REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Contrôles avant publication
env:
SLUG: ${{ steps.meta.outputs.slug }}
run: |
ref_min="$(cat tools/config/min-updater-version.txt)"
main_min="$(cat tools-main/config/min-updater-version.txt)"
min="$(printf '%s\n%s\n' "$ref_min" "$main_min" | sort -V | tail -n 1)"
php tools/scripts/check-headers.php plugin "$SLUG" "$GITHUB_REF_NAME" "$min"

- name: Syntaxe PHP
run: find plugin -name '*.php' -not -path '*/node_modules/*' -print0 | xargs -0 -n1 php -l > /dev/null

- name: Dépendances Composer
if: hashFiles('plugin/composer.json') != ''
working-directory: plugin
run: composer install --no-dev --prefer-dist --no-interaction --no-progress --optimize-autoloader

- name: Assets
if: hashFiles('plugin/package.json') != ''
working-directory: plugin
run: npm ci && npm run build

- name: Paquet
env:
SLUG: ${{ steps.meta.outputs.slug }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
mkdir -p "build/$SLUG" dist
rsync -a --exclude-from=plugin/.distignore --exclude=.git --exclude=.github plugin/ "build/$SLUG/"
php tools/scripts/check-package.php "build/$SLUG" "$SLUG" "$VERSION"
(cd build && zip -rqX "../dist/$SLUG-$VERSION.zip" "$SLUG")

- name: Conservation du paquet
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: paquet
path: dist/
retention-days: 1
if-no-files-found: error
overwrite: true

sign-upload:
name: Signature et envoi
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
concurrency:
group: release-upload-${{ github.repository }}
cancel-in-progress: false
steps:
- name: Outils de publication
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: wearewp/release-tools
ref: ${{ inputs.tools-ref }}
path: tools
persist-credentials: false

- name: Récupération du paquet
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: paquet
path: dist

- name: Installation de lftp
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq lftp > /dev/null

- name: Signature
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
ZIP: dist/${{ needs.build.outputs.slug }}-${{ needs.build.outputs.version }}.zip
run: php tools/scripts/sign.php "$ZIP"

- name: Envoi FTPS vers le serveur de mises à jour
env:
FTP_HOST: ${{ secrets.FTP_HOST }}
FTP_USER: ${{ secrets.FTP_USER }}
LFTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
ZIP: dist/${{ needs.build.outputs.slug }}-${{ needs.build.outputs.version }}.zip
run: bash tools/scripts/upload-ftps.sh "$ZIP"

- name: Conservation de la signature
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: signature
path: dist/*.sig
retention-days: 1
if-no-files-found: error
overwrite: true

publish-release:
name: Archive dans la Release GitHub
needs: [build, sign-upload]
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: write
steps:
- name: Récupération du paquet
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: paquet
path: dist

- name: Récupération de la signature
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: signature
path: dist

- name: Release
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
assets="$(gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name')"
for file in dist/*; do
while IFS= read -r asset; do
if [ "$asset" = "$(basename "$file")" ]; then
echo "La Release $GITHUB_REF_NAME contient déjà $asset : aucun remplacement automatique. Vérifier l'asset existant et le supprimer à la main s'il doit être remplacé, puis relancer le job." >&2
exit 1
fi
done <<< "$assets"
done
gh release upload "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY"
else
gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --notes "Paquet signé publié sur updates.wearewp.pro."
fi
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# release-tools

Outils de publication des plugins WeAre[WP] et client de mise à jour `wawp-updater`.

- `wawp-updater/` : bibliothèque embarquée dans chaque plugin (PHP 7.4+, WordPress 5.8+). Vérifie les mises à jour sur `updates.wearewp.pro` avec la clé `WAWP_LICENSE_KEY` et refuse tout paquet dont la signature Ed25519 est invalide.
- `.github/workflows/plugin-release.yml` : workflow réutilisable, déclenché par un tag `vX.Y.Z` dans le dépôt d'un plugin.
- `scripts/`, `bin/` : contrôles, signature, envoi FTPS, génération des clés, préparation d'un dépôt.

Ce dépôt ne contient aucun secret. Mise en place : `docs/mise-en-place.md`.

Développement : `composer test`, `composer stan`, `composer cs`, `composer e2e` (nécessite le dépôt `updates-server` à côté).

Licence : GPL-2.0-or-later.
40 changes: 40 additions & 0 deletions bin/keygen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Génère les deux paires de clés Ed25519 (active et secours). N'écrit rien sur disque.
* À lancer uniquement dans le terminal de Thierry, jamais dans une session automatisée.
*/

declare(strict_types=1);

if (PHP_SAPI !== 'cli') {
exit(1);
}

$pairs = [];
foreach (['ACTIVE', 'SECOURS'] as $role) {
$keypair = sodium_crypto_sign_keypair();
$pairs[$role] = [
'secret' => base64_encode(sodium_crypto_sign_secretkey($keypair)),
'public' => base64_encode(sodium_crypto_sign_publickey($keypair)),
];
sodium_memzero($keypair);
}

$active = $pairs['ACTIVE'];
$backup = $pairs['SECOURS'];

echo "=== Clé ACTIVE ===\n";
echo "Privée (secret d'organisation GitHub SIGNING_KEY, et copie dans le gestionnaire de mots de passe) :\n";
echo $active['secret'] . "\n";
echo "Publique :\n";
echo $active['public'] . "\n\n";
echo "=== Clé de SECOURS ===\n";
echo "Privée (gestionnaire de mots de passe UNIQUEMENT, jamais sur GitHub ni sur un serveur) :\n";
echo $backup['secret'] . "\n";
echo "Publique :\n";
echo $backup['public'] . "\n\n";
echo "=== Contenu de ~/updates-data/signing.pub sur o2switch ===\n";
echo "# active\n{$active['public']}\n# secours\n{$backup['public']}\n\n";
echo "=== Ligne à placer dans wawp-updater/src/class-config.php ===\n";
echo "const PUBLIC_KEYS = array( '{$active['public']}', '{$backup['public']}' );\n\n";
echo "Rien n'a été écrit sur disque. Effacez ce terminal après avoir copié les valeurs.\n";
69 changes: 69 additions & 0 deletions bin/setup-repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Prépare un dépôt de plugin pour la publication automatique. Ne manipule jamais la valeur d'un secret.
# Usage : bash bin/setup-repo.sh wearewp/<slug> <chemin-local-du-plugin>
set -euo pipefail

repo="${1:-}"
path="${2:-}"
tools="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

if [[ ! "$repo" =~ ^wearewp/[a-z0-9-]{1,64}$ ]]; then
echo "Dépôt attendu sous la forme wearewp/<slug> (slug en minuscules)." >&2
exit 1
fi
if [ -z "$path" ] || [ ! -d "$path" ]; then
echo "Chemin local du plugin introuvable : $path" >&2
exit 1
fi
slug="${repo#wearewp/}"

visibility="$(gh api "repos/$repo" --jq '.visibility')"
if [ "$visibility" != "private" ]; then
echo "Le dépôt $repo doit être privé (visibilité actuelle : $visibility)." >&2
exit 1
fi
repo_id="$(gh api "repos/$repo" --jq '.id')"

for secret in SIGNING_KEY FTP_HOST FTP_USER FTP_PASSWORD; do
gh api -X PUT "orgs/wearewp/actions/secrets/$secret/repositories/$repo_id" > /dev/null
echo "Secret $secret accessible à $repo."
done

rulesets="$(gh api "repos/$repo/rulesets" --jq '.[].name')"
if grep -qx 'Tags de version immuables' <<< "$rulesets"; then
echo "Ruleset déjà présent."
else
gh api -X POST "repos/$repo/rulesets" --input - > /dev/null <<'JSON'
{
"name": "Tags de version immuables",
"target": "tag",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["refs/tags/v*"], "exclude": [] } },
"rules": [ { "type": "deletion" }, { "type": "update" }, { "type": "non_fast_forward" } ]
}
JSON
echo "Ruleset « Tags de version immuables » créé."
fi

mkdir -p "$path/.github/workflows"
if [ -f "$path/.github/workflows/release.yml" ]; then
echo "release.yml déjà présent : non modifié."
else
cp "$tools/templates/release.yml" "$path/.github/workflows/release.yml"
echo "release.yml créé : à commiter."
fi

status=0
if [ ! -f "$path/.distignore" ]; then
echo "À faire : créer $path/.distignore (fichiers exclus du paquet)." >&2
status=1
fi
if ! grep -qE '^[[:space:]*#@/]*Update URI:[[:space:]]*https://updates\.wearewp\.pro[[:space:]]*$' "$path/$slug.php" 2>/dev/null; then
echo "À faire : ajouter l'en-tête « Update URI: https://updates.wearewp.pro » dans $slug.php." >&2
status=1
fi
if [ ! -f "$path/wawp-updater/load.php" ]; then
echo "À faire : copier wawp-updater/ dans le plugin et ajouter le chargement (voir docs/mise-en-place.md)." >&2
status=1
fi
exit "$status"
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"process-timeout": 900,
"sort-packages": true
},
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions config/min-updater-version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
Loading
Loading