From 406f6ab1b5d7eb9a524f3f8d8ddf4fa516c57d2a Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 12:49:50 +0200 Subject: [PATCH 1/6] build: validation des ancres des liens internes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un lien vers une ancre absente (titre renommé, par exemple) fait désormais échouer le build strict. Aucun lien cassé à ce jour. Co-authored-by: Claude --- mkdocs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 3fbb9f9..9b271d8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,11 @@ edit_uri: edit/main/docs/ copyright: Copyright © 2026 OTSPI — Contenu sous licence Creative Commons Attribution 4.0 International (CC-BY-4.0). +# Liens internes : un lien vers une ancre absente (titre renommé, par exemple) fait échouer +# le build strict, comme un lien vers une page absente. +validation: + anchors: warn + hooks: - hooks/page_lang.py From 641c1d11e7f80944d2d79551f7d646410a82a471 Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 12:50:21 +0200 Subject: [PATCH 2/6] =?UTF-8?q?build:=20recherche=20en=20fran=C3=A7ais=20e?= =?UTF-8?q?t=20en=20anglais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le livre blanc et la page des hébergements existent en anglais : l'index de recherche applique désormais les règles des deux langues (racines, mots vides), et « authorities » trouve les pages anglaises. Co-authored-by: Claude --- mkdocs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 9b271d8..5496717 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -73,8 +73,11 @@ extra_css: - stylesheets/print.css plugins: + # Recherche en français et en anglais (livre blanc et page des hébergements existent dans les deux langues) - search: - lang: fr + lang: + - fr + - en markdown_extensions: - admonition From f715dafce4afd5d1987d6d939700eb18cbc67c9f Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 12:51:05 +0200 Subject: [PATCH 3/6] =?UTF-8?q?docs:=20section=20Outils=20du=20README=20et?= =?UTF-8?q?=20arborescence=20=C3=A0=20jour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Commandes de construction et de prévisualisation, chaîne de contrôle complète (build strict, PDF, PDF/UA-1, accessibilité) et tableau des scripts avec leurs prérequis. - Arborescence alignée sur le dépôt : livre blanc, overrides, scripts, workflows ; plus de docs/CNAME inexistant. Les statuts y sont décrits comme un projet non encore adopté, comme sur le portail. Co-authored-by: Claude --- README.md | 85 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c26b7b1..39a3c3e 100644 --- a/README.md +++ b/README.md @@ -42,38 +42,75 @@ La gouvernance d'OTSPI applique une séparation stricte des devoirs conformémen ## 📁 Panoplie Documentaire Complète ``` -├── docs/ -│ ├── index.md # Page d'accueil du portail web about.otspi.org -│ ├── CNAME # Configuration du domaine personnalisé (about.otspi.org) +├── docs/ # Contenu du portail about.otspi.org +│ ├── index.md # Page d'accueil (bandeau dans overrides/home.html) +│ ├── livre-blanc/ # Livre blanc (FR, fait foi) et historique des versions +│ ├── white-paper/ # Traduction anglaise du livre blanc │ ├── statuts/ -│ │ └── statuts-association.md # Statuts constitutifs adoptés (OTSPI, Lyon, 20 septembre 2026) +│ │ └── statuts-association.md # Projet de statuts, soumis à l'AG constitutive (non encore adopté) │ ├── reglement-interieur/ -│ │ └── reglement-interieur.md # RI complet : cursus Officiers, MFA FIPS/ANSSI, Key Custodians, dépenses -│ ├── administratif/ -│ │ ├── pv-ag-constitutive-modele.md # PV d'AG Constitutive (élections, mandats fiscaux L.80 CB, RC Pro) -│ │ ├── rescrit-fiscal-mecenat.md # Demande formelle de rescrit fiscal DGFIP (Méthode 4P) -│ │ └── declaration-prefecture.md # Guide des formalités préfecture (RNA, JOAFE, SIRET) -│ ├── cadrage/ -│ │ ├── index.md # Présentation du socle d'audit initial TSP / PKI -│ │ ├── cp-cps-cadre.md # Cadre général CP/CPS conforme RFC 3647 & ETSI EN 319 411 -│ │ ├── pssi.md # PSSI (ISO 27001 & ETSI EN 319 401, RSSI, incidents) -│ │ └── termination-plan.md # Plan de fin d'activité (archivage 10-30 ans, séquestre financier) +│ │ └── reglement-interieur.md # RI : cursus Officiers, MFA FIPS/ANSSI, Key Custodians, dépenses │ ├── gouvernance/ -│ ├── charte-ethique.md # Charte d'éthique, de déontologie et de gestion désintéressée -│ │ └── comite-technique.md # Articulation CPC (PMA), TSC (ingénierie logicielle) & RFCs +│ │ ├── charte-ethique.md # Charte d'éthique, de déontologie et de transparence publique +│ │ └── comite-technique.md # Articulation CPC (PMA), TSC (ingénierie logicielle) et RFC +│ ├── administratif/ # PV d'AG constitutive (modèle), rescrit fiscal, guide préfecture… +│ ├── cadrage/ # Socle d'audit TSP / PKI : CP/CPS, PSSI, fin d'activité, OID, devis │ ├── adhesion/ -│ │ └── bulletin-adhesion.md # Formulaires d'adhésion (sympathisants, titulaires, bienfaiteurs) -│ └── reunions/ -│ └── index.md # Registre public des réunions et procès-verbaux (caviardage RGPD) -├── mkdocs.yml # Configuration Material for MkDocs (about.otspi.org) -├── .github/workflows/ -│ └── deploy-o2switch.yml # Déploiement automatique chez o2switch (FTPS) -├── LICENSE # Licence Creative Commons Attribution 4.0 International -└── README.md # Documentation générale du dépôt +│ │ └── bulletin-adhesion.md # Formulaires d'adhésion (sympathisants, titulaires, bienfaiteurs) +│ ├── reunions/ # Registre des réunions, transparence des hébergements (FR / EN) +│ ├── assets/ # Logos, police Inter (OFL), image de partage +│ ├── stylesheets/ # Charte du portail (extra.css), impression et PDF (print.css) +│ └── javascripts/ # Accessibilité, mesure d'audience sans cookie +├── overrides/ # Gabarits Material : accueil, 404, métadonnées de partage +├── hooks/page_lang.py # Langue de la balise selon la page +├── scripts/ # Outils : voir ci-dessous +├── data/qtsp-count.json # Décompte des QTSP cité par le livre blanc (annexe C) +├── deploy/o2switch/ # Configuration Apache (.htaccess) et notes de déploiement +├── .github/workflows/ # Déploiement, vérification des PR, contrôles périodiques +├── mkdocs.yml # Configuration Material for MkDocs +└── LICENSE # Creative Commons Attribution 4.0 International ``` --- +## 🛠️ Outils et contribution + +Le portail est construit avec [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). Toute pull request vers `main` est vérifiée par `.github/workflows/check-pr.yml` (construction stricte, accessibilité, PDF) ; la fusion déclenche le déploiement (`deploy-o2switch.yml`). + +**Construire et prévisualiser le site** + +```sh +pip install -r requirements.txt +mkdocs serve # http://127.0.0.1:8000, rechargé à chaque modification +mkdocs build --strict # échoue sur tout lien ou ancre interne cassé +``` + +**Scripts** (`scripts/`) + +| Script | Rôle | Prérequis | +|---|---|---| +| `export_pdf.py` | PDF balisés du livre blanc (FR, EN), du projet de statuts, du règlement intérieur et de la charte, dans `site/` | `pip install -r requirements-pdf.txt` (WeasyPrint, Pango) | +| `check_pdf_ua.sh` | Validation PDF/UA-1 de ces PDF avec veraPDF (téléchargé et vérifié au premier lancement) | Java | +| `check_a11y.js` | Accessibilité WCAG 2 AA (pa11y, moteur axe) de toutes les pages du site construit | Node.js, `npm install --no-save pa11y@9`, Chrome | +| `build_diagrams.py` | Régénère les schémas SVG du livre blanc et l'organigramme de l'accueil | — | +| `build_og_image.py` | Régénère l'image de partage par défaut (`docs/assets/og/og-portail.png`) | Chrome | +| `count_qtsp.py` | Décompte des QTSP des listes de confiance de l'EEE (`data/qtsp-count.json`) | — | +| `check_links.py` | Liens externes cassés (contrôle mensuel) | — | +| `check_sites.sh` | Codes HTTP, redirections et certificats des sites OTSPI (contrôle quotidien) | curl, openssl | + +Chaîne complète, comme en CI : + +```sh +mkdocs build --strict +python scripts/export_pdf.py +bash scripts/check_pdf_ua.sh +npm install --no-save pa11y@9 && node scripts/check_a11y.js site +``` + +Les schémas et l'image de partage sont générés puis versionnés : après modification de leur source, relancer le script correspondant et commiter le résultat. + +--- + ## 📬 Contact - **Contact unique (Adhésions, Questions générales, Sécurité)** : `contact@otspi.org` From 6ca29106210ab164000fefb58777199d59195759 Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 12:51:44 +0200 Subject: [PATCH 4/6] =?UTF-8?q?ci:=20action=20commune=20au=20d=C3=A9ploiem?= =?UTF-8?q?ent=20et=20=C3=A0=20la=20v=C3=A9rification=20des=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .github/actions/build-portal regroupe l'installation, la construction stricte, le contrôle d'accessibilité, l'export des PDF et la validation PDF/UA-1. Le déploiement passe désormais aussi le contrôle d'accessibilité : un site qui échouerait en PR n'est pas publié. Co-authored-by: Claude --- .github/actions/build-portal/action.yml | 55 +++++++++++++++++++++++++ .github/workflows/check-pr.yml | 45 ++------------------ .github/workflows/deploy-o2switch.yml | 31 +++----------- README.md | 2 +- deploy/o2switch/README.md | 2 +- 5 files changed, 65 insertions(+), 70 deletions(-) create mode 100644 .github/actions/build-portal/action.yml diff --git a/.github/actions/build-portal/action.yml b/.github/actions/build-portal/action.yml new file mode 100644 index 0000000..4c7db93 --- /dev/null +++ b/.github/actions/build-portal/action.yml @@ -0,0 +1,55 @@ +name: Construction et contrôle du portail +description: >- + Étapes communes au déploiement et à la vérification des pull requests : construction stricte du + site, contrôle d'accessibilité des pages, export des PDF et validation PDF/UA-1. Le site construit + reste dans ./site pour les étapes suivantes du workflow appelant. + +runs: + using: composite + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install dependencies + shell: bash + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-pdf.txt + sudo apt-get update + sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 poppler-utils + + - name: Build documentation + shell: bash + run: mkdocs build --strict + + # Accessibilité des pages HTML (WCAG 2 AA, moteur axe) : Chrome du runner, sans téléchargement + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Check accessibility (pa11y, axe) + shell: bash + env: + PUPPETEER_SKIP_DOWNLOAD: "true" + CHROME: /usr/bin/google-chrome + run: | + npm install --no-save --no-audit --no-fund pa11y@9 + node scripts/check_a11y.js site + + - name: Export PDF + shell: bash + run: python scripts/export_pdf.py + + - name: Cache veraPDF + uses: actions/cache@v4 + with: + path: ~/.cache/verapdf + key: verapdf-1.30.2 + + - name: Validate PDF/UA-1 + shell: bash + run: bash scripts/check_pdf_ua.sh diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index a6c944e..c7526e5 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -1,6 +1,6 @@ name: Vérification des pull requests (build, accessibilité et PDF) -# Mêmes étapes de construction que le déploiement, sans déploiement ni secret : +# Mêmes étapes de construction que le déploiement (.github/actions/build-portal), sans déploiement ni secret : # le site et les PDF du portail (livre blanc, statuts, règlement intérieur, charte) sont validés # avant fusion, et les PDF joints en artefact pour relecture. @@ -24,47 +24,8 @@ jobs: with: persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt -r requirements-pdf.txt - sudo apt-get update - sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 poppler-utils - - - name: Build documentation - run: mkdocs build --strict - - # Accessibilité des pages HTML (WCAG 2 AA, moteur axe) : Chrome du runner, sans téléchargement - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - - - name: Check accessibility (pa11y, axe) - env: - PUPPETEER_SKIP_DOWNLOAD: "true" - CHROME: /usr/bin/google-chrome - run: | - npm install --no-save --no-audit --no-fund pa11y@9 - node scripts/check_a11y.js site - - - name: Export PDF - run: python scripts/export_pdf.py - - - name: Cache veraPDF - uses: actions/cache@v4 - with: - path: ~/.cache/verapdf - key: verapdf-1.30.2 - - - name: Validate PDF/UA-1 - run: bash scripts/check_pdf_ua.sh + - name: Build and check the portal + uses: ./.github/actions/build-portal # Polices embarquées : Inter pour le texte ; toute autre police signale un glyphe absent d'Inter - name: List embedded fonts diff --git a/.github/workflows/deploy-o2switch.yml b/.github/workflows/deploy-o2switch.yml index 922a784..caa8aad 100644 --- a/.github/workflows/deploy-o2switch.yml +++ b/.github/workflows/deploy-o2switch.yml @@ -21,33 +21,12 @@ jobs: with: persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt -r requirements-pdf.txt - sudo apt-get update - sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 lftp - - - name: Build documentation - run: mkdocs build --strict - - - name: Export whitepaper PDF - run: python scripts/export_pdf.py - - - name: Cache veraPDF - uses: actions/cache@v4 - with: - path: ~/.cache/verapdf - key: verapdf-1.30.2 + # Construction stricte, accessibilité, PDF et PDF/UA-1 : mêmes contrôles que les pull requests + - name: Build and check the portal + uses: ./.github/actions/build-portal - - name: Validate PDF/UA-1 - run: bash scripts/check_pdf_ua.sh + - name: Install lftp + run: sudo apt-get install -y lftp - name: Add Apache configuration run: cp deploy/o2switch/htaccess site/.htaccess diff --git a/README.md b/README.md index 39a3c3e..1b9ef52 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ La gouvernance d'OTSPI applique une séparation stricte des devoirs conformémen ## 🛠️ Outils et contribution -Le portail est construit avec [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). Toute pull request vers `main` est vérifiée par `.github/workflows/check-pr.yml` (construction stricte, accessibilité, PDF) ; la fusion déclenche le déploiement (`deploy-o2switch.yml`). +Le portail est construit avec [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). Toute pull request vers `main` est vérifiée par `.github/workflows/check-pr.yml` (construction stricte, accessibilité, PDF) ; la fusion déclenche le déploiement (`deploy-o2switch.yml`), qui repasse les mêmes contrôles. Les deux workflows partagent l'action `.github/actions/build-portal`. **Construire et prévisualiser le site** diff --git a/deploy/o2switch/README.md b/deploy/o2switch/README.md index a327d13..1fce292 100644 --- a/deploy/o2switch/README.md +++ b/deploy/o2switch/README.md @@ -5,7 +5,7 @@ Ce dossier décrit l'hébergement du portail `about.otspi.org` chez o2switch (so ## Ce qui est en place - `htaccess` : redirection HTTPS, domaine canonique, page 404, en-têtes de sécurité, compression et durées de cache. Il est copié à la racine du site au déploiement. -- `.github/workflows/deploy-o2switch.yml` : construit le site, exporte et valide les PDF (PDF/UA-1), ajoute le `.htaccess`, puis envoie tout en FTPS (TLS obligatoire, certificat vérifié). +- `.github/workflows/deploy-o2switch.yml` : construit et contrôle le site avec l'action commune `.github/actions/build-portal` (construction stricte, accessibilité, export et validation PDF/UA-1 des PDF, mêmes contrôles que les pull requests), ajoute le `.htaccess`, puis envoie tout en FTPS (TLS obligatoire, certificat vérifié). - Un compte FTP dédié, cantonné au seul répertoire du site. Il est renseigné dans les secrets `O2_FTP_USERNAME` et `O2_FTP_PASSWORD` du dépôt ; l'hôte est dans la variable `O2_FTP_HOST`. ## Bascule (faite le 26 septembre 2026) From ef741b1d116859b30ee15d030711f59f1feffc52 Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 13:05:27 +0200 Subject: [PATCH 5/6] docs(statuts): historique du projet de statuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Page « Historique du projet de statuts » sur le modèle de celle du livre blanc : modifications de fond datées, depuis le premier projet « Open eIDAS » du 6 septembre 2026, établies à partir de l'historique Git. Co-authored-by: Claude --- docs/statuts/historique.md | 15 +++++++++++++++ mkdocs.yml | 1 + 2 files changed, 16 insertions(+) create mode 100644 docs/statuts/historique.md diff --git a/docs/statuts/historique.md b/docs/statuts/historique.md new file mode 100644 index 0000000..2d11f33 --- /dev/null +++ b/docs/statuts/historique.md @@ -0,0 +1,15 @@ +# Historique du projet de statuts + +**Ce qui a changé dans le projet, et quand** + +Les statuts d'OTSPI sont un **projet**, soumis au vote de l'assemblée générale constitutive, qui n'a pas encore eu lieu. Cette page retrace les modifications de fond apportées au projet depuis sa première rédaction ; le [dépôt du portail](https://github.com/otspi/organisation/commits/main/docs/statuts/statuts-association.md) conserve l'historique complet, ligne à ligne. + +| Date | Modification | +|---|---| +| 6 septembre 2026 | Premier projet, sous le nom « Open eIDAS » : association d'intérêt général à but non lucratif, consacrée à des infrastructures de confiance numérique ouvertes. Le même jour : conformité aux critères de l'intérêt général (articles 200 et 238 bis du CGI) et aux principes des associations reconnues d'utilité publique ; bénévolat strict des dirigeants, avec la possibilité d'employer un personnel technique rémunéré sans rôle dans la gouvernance ; contribution au prix coûtant pour les usages massifs, recettes intégralement réinvesties. | +| 20 septembre 2026 | Refonte sous le nom « Open Trusted Service Provider Initiative » (OTSPI), avec le règlement intérieur et les documents de gouvernance. Création du statut de membre bienfaiteur, sans droit de vote. | +| 21 septembre 2026 | Déchéance d'un membre titulaire portée de deux à trois absences consécutives à l'assemblée générale ordinaire (article 5 ter). | +| 25 septembre 2026 | Texte présenté comme un projet non encore adopté ; la date de signature est laissée à compléter le jour de l'assemblée générale constitutive. Ajout de la vacance initiale du CPC (article 8 bis, alinéa 5 bis) : tant que le comité n'est pas constitué, ses compétences exclusives ne peuvent être exercées par aucun autre organe. | +| 27 septembre 2026 | Mise en forme, sans modification du texte : listes rétablies. Version PDF balisée, téléchargeable depuis la page du projet de statuts. | + +Le [règlement intérieur](../reglement-interieur/reglement-interieur.md) et la [charte d'éthique](../gouvernance/charte-ethique.md) suivent le projet de statuts ; leurs modifications sont consultables dans le même dépôt. diff --git a/mkdocs.yml b/mkdocs.yml index 5496717..b33d237 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,6 +126,7 @@ nav: - Historique des versions: livre-blanc/historique.md - Statuts et gouvernance: - Projet de statuts: statuts/statuts-association.md + - Historique du projet de statuts: statuts/historique.md - Charte d'éthique: gouvernance/charte-ethique.md - Organisation technique et CPC: gouvernance/comite-technique.md - Règlement intérieur: From 04225f669c4bdfab8e0c2f6ef7575ea739d3d836 Mon Sep 17 00:00:00 2001 From: Philippe Vienne Date: Sun, 27 Sep 2026 13:06:40 +0200 Subject: [PATCH 6/6] ci: release avec PDF joints et brouillon Zenodo du livre blanc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit À la publication d'une release (étiquette vX.Y), le portail est reconstruit et contrôlé, les cinq PDF sont joints à la release et, si le secret ZENODO_TOKEN existe, le livre blanc (FR, EN) est déposé sur Zenodo en brouillon avec les métadonnées de .zenodo.json. Le DOI, définitif, n'est attribué qu'à la publication manuelle depuis Zenodo. Co-authored-by: Claude --- .github/workflows/release.yml | 48 ++++++++++++++++++ README.md | 1 + scripts/zenodo_draft.py | 95 +++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/zenodo_draft.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b0b14ce --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: Release (PDF et brouillon Zenodo) + +# À la publication d'une release GitHub (étiquette vX.Y, numéro de version du livre blanc) : +# - reconstruit et contrôle le portail à cette étiquette, puis joint à la release les cinq PDF +# (livre blanc FR et EN, projet de statuts, règlement intérieur, charte d'éthique) ; +# - si le secret ZENODO_TOKEN existe, dépose le livre blanc sur Zenodo en brouillon +# (scripts/zenodo_draft.py) : le DOI n'est attribué qu'à la publication manuelle depuis Zenodo. +# Ne pas activer en plus l'intégration GitHub de Zenodo, qui créerait un second dépôt par release. + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + persist-credentials: false + + - name: Build and check the portal + uses: ./.github/actions/build-portal + + - name: Attach PDF to the release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$TAG" $(find site -name 'otspi-*.pdf' | sort) --clobber --repo "$GITHUB_REPOSITORY" + + - name: Zenodo draft (white paper) + env: + ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }} + ZENODO_URL: ${{ vars.ZENODO_URL }} + ZENODO_DEPOSITION_ID: ${{ vars.ZENODO_DEPOSITION_ID }} + TAG: ${{ github.event.release.tag_name }} + run: | + if [ -z "$ZENODO_TOKEN" ]; then + echo "Secret ZENODO_TOKEN absent : pas de dépôt Zenodo." | tee -a "$GITHUB_STEP_SUMMARY" + exit 0 + fi + python scripts/zenodo_draft.py --version "${TAG#v}" \ + site/livre-blanc/otspi-livre-blanc.pdf site/white-paper/otspi-white-paper.pdf diff --git a/README.md b/README.md index 1b9ef52..032bb85 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ mkdocs build --strict # échoue sur tout lien ou ancre interne cassé | `check_a11y.js` | Accessibilité WCAG 2 AA (pa11y, moteur axe) de toutes les pages du site construit | Node.js, `npm install --no-save pa11y@9`, Chrome | | `build_diagrams.py` | Régénère les schémas SVG du livre blanc et l'organigramme de l'accueil | — | | `build_og_image.py` | Régénère l'image de partage par défaut (`docs/assets/og/og-portail.png`) | Chrome | +| `zenodo_draft.py` | Brouillon Zenodo du livre blanc à la publication d'une release (`release.yml`) ; le DOI n'est attribué qu'à la publication manuelle sur Zenodo | secret `ZENODO_TOKEN` | | `count_qtsp.py` | Décompte des QTSP des listes de confiance de l'EEE (`data/qtsp-count.json`) | — | | `check_links.py` | Liens externes cassés (contrôle mensuel) | — | | `check_sites.sh` | Codes HTTP, redirections et certificats des sites OTSPI (contrôle quotidien) | curl, openssl | diff --git a/scripts/zenodo_draft.py b/scripts/zenodo_draft.py new file mode 100644 index 0000000..b07ab51 --- /dev/null +++ b/scripts/zenodo_draft.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: EUPL-1.2 +"""Prépare sur Zenodo un dépôt en brouillon du livre blanc (PDF français et anglais) pour une version donnée. + +Le dépôt reste un brouillon : le DOI n'est attribué, définitivement, que lorsqu'une personne relit puis +publie le dépôt depuis Zenodo. Les métadonnées viennent de .zenodo.json ; la version et la date de +publication sont ajoutées. Appelé par .github/workflows/release.yml à la publication d'une release. + +Variables d'environnement : + ZENODO_TOKEN jeton personnel Zenodo (portées deposit:write et deposit:actions) ; + ZENODO_URL https://zenodo.org par défaut (https://sandbox.zenodo.org pour un essai) ; + ZENODO_DEPOSITION_ID identifiant d'un dépôt déjà publié : le brouillon en devient une nouvelle + version (même DOI de concept) au lieu d'un dépôt distinct. + +Usage : + python3 scripts/zenodo_draft.py --version 0.9 fichier.pdf [...] + python3 scripts/zenodo_draft.py --version 0.9 --dry-run fichier.pdf [...] # affiche sans envoyer + +Aucune dépendance en dehors de la bibliothèque standard. +""" + +import argparse +import datetime +import json +import os +import sys +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +def api(method, url, token, payload=None, data=None, content_type="application/json"): + body = json.dumps(payload).encode("utf-8") if payload is not None else data + request = urllib.request.Request(url, data=body, method=method) + request.add_header("Authorization", f"Bearer {token}") + if body is not None: + request.add_header("Content-Type", content_type) + with urllib.request.urlopen(request, timeout=300) as response: + raw = response.read() + return json.loads(raw) if raw else None + + +def metadata(version): + meta = json.loads((ROOT / ".zenodo.json").read_text(encoding="utf-8")) + meta["version"] = version + meta["publication_date"] = datetime.date.today().isoformat() + return meta + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--version", required=True, help="numéro de version (par exemple 0.9)") + parser.add_argument("--dry-run", action="store_true", help="afficher les métadonnées et les fichiers sans rien envoyer") + parser.add_argument("files", nargs="+", type=Path, help="fichiers à déposer") + args = parser.parse_args() + + missing = [str(f) for f in args.files if not f.is_file()] + if missing: + sys.exit(f"Fichiers introuvables : {', '.join(missing)}") + meta = metadata(args.version) + if args.dry_run: + print(json.dumps({"metadata": meta, "files": [f.name for f in args.files]}, ensure_ascii=False, indent=2)) + return + + token = os.environ.get("ZENODO_TOKEN") + if not token: + sys.exit("ZENODO_TOKEN n'est pas défini.") + base = os.environ.get("ZENODO_URL", "https://zenodo.org").rstrip("/") + previous = os.environ.get("ZENODO_DEPOSITION_ID") + + if previous: + # Nouvelle version d'un dépôt publié : Zenodo recopie ses fichiers, qu'on remplace + created = api("POST", f"{base}/api/deposit/depositions/{previous}/actions/newversion", token) + draft = api("GET", created["links"]["latest_draft"], token) + for old in draft.get("files", []): + api("DELETE", old["links"]["self"], token) + else: + draft = api("POST", f"{base}/api/deposit/depositions", token, payload={}) + + for path in args.files: + api("PUT", f'{draft["links"]["bucket"]}/{path.name}', token, data=path.read_bytes(), content_type="application/octet-stream") + draft = api("PUT", f'{base}/api/deposit/depositions/{draft["id"]}', token, payload={"metadata": meta}) + + link = draft["links"]["html"] + print(f"Brouillon Zenodo prêt (non publié, aucun DOI attribué) : {link}") + summary = os.environ.get("GITHUB_STEP_SUMMARY") + if summary: + with open(summary, "a", encoding="utf-8") as out: + out.write(f"Brouillon Zenodo de la version {args.version} : {link}\n\n" + "À relire puis publier depuis Zenodo pour attribuer le DOI.\n") + + +if __name__ == "__main__": + main()