From e769984e9578162837155ae2ca71428b9ce59420 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Tue, 8 Sep 2026 15:01:58 +0200 Subject: [PATCH 1/2] docs: bilingual docs by language folder (docs/fr + docs/en) - Translate all 12 docs to French; keep English versions. - Organise by language FOLDER (docs/fr/, docs/en/) instead of a .en.md suffix, with a language switcher at the top of each and folder-relative cross-links. - README.md (FR) links -> docs/fr/*, README.en.md links -> docs/en/*. - README: drop the confusing 'Aurora DSQL/Bedrock not used' bullet; trim the Terraform prerequisite wording. --- README.en.md | 15 ++-- README.md | 19 ++-- docs/{ => en}/cron.md | 2 + docs/{ => en}/db-bootstrap.md | 8 +- docs/{ => en}/dsql.md | 2 + docs/{ => en}/environments.md | 10 ++- docs/{ => en}/logging.md | 2 + docs/{ => en}/plugins.md | 6 +- docs/{ => en}/s3-storage.md | 2 + docs/{ => en}/secrets.md | 2 + docs/{ => en}/sessions.md | 2 + docs/en/spip-passwords.md | 106 ++++++++++++++++++++++ docs/{ => en}/spip-upgrade.md | 10 ++- docs/{ => en}/tracing.md | 2 + docs/fr/cron.md | 80 +++++++++++++++++ docs/fr/db-bootstrap.md | 135 ++++++++++++++++++++++++++++ docs/fr/dsql.md | 152 +++++++++++++++++++++++++++++++ docs/fr/environments.md | 73 +++++++++++++++ docs/fr/logging.md | 112 +++++++++++++++++++++++ docs/fr/plugins.md | 140 +++++++++++++++++++++++++++++ docs/fr/s3-storage.md | 138 ++++++++++++++++++++++++++++ docs/fr/secrets.md | 84 ++++++++++++++++++ docs/fr/sessions.md | 94 ++++++++++++++++++++ docs/{ => fr}/spip-passwords.md | 4 +- docs/fr/spip-upgrade.md | 78 ++++++++++++++++ docs/fr/tracing.md | 153 ++++++++++++++++++++++++++++++++ 26 files changed, 1399 insertions(+), 32 deletions(-) rename docs/{ => en}/cron.md (98%) rename docs/{ => en}/db-bootstrap.md (96%) rename docs/{ => en}/dsql.md (99%) rename docs/{ => en}/environments.md (89%) rename docs/{ => en}/logging.md (98%) rename docs/{ => en}/plugins.md (97%) rename docs/{ => en}/s3-storage.md (98%) rename docs/{ => en}/secrets.md (98%) rename docs/{ => en}/sessions.md (98%) create mode 100644 docs/en/spip-passwords.md rename docs/{ => en}/spip-upgrade.md (91%) rename docs/{ => en}/tracing.md (99%) create mode 100644 docs/fr/cron.md create mode 100644 docs/fr/db-bootstrap.md create mode 100644 docs/fr/dsql.md create mode 100644 docs/fr/environments.md create mode 100644 docs/fr/logging.md create mode 100644 docs/fr/plugins.md create mode 100644 docs/fr/s3-storage.md create mode 100644 docs/fr/secrets.md create mode 100644 docs/fr/sessions.md rename docs/{ => fr}/spip-passwords.md (98%) create mode 100644 docs/fr/spip-upgrade.md create mode 100644 docs/fr/tracing.md diff --git a/README.en.md b/README.en.md index d652e74..9f22808 100644 --- a/README.en.md +++ b/README.en.md @@ -45,12 +45,11 @@ skeletons. Default region: **eu-west-3** (Paris) đŸ‡«đŸ‡·. - An AWS account with permission to create the resources above (Aurora DSQL is available in eu-west-3 among others). - **Docker** installed and **running** (buildx; the image is `linux/arm64`). -- **Terraform ≄ 1.14** (an older 1.5.x on your PATH will fail the `required_version` check). +- **Terraform ≄ 1.14**. - **AWS CLI v2** with an **active session** for your profile — `aws sso login --profile

` or exported credentials. The principal must be able to create DSQL, S3, ECR, DynamoDB, SSM, Lambda, API Gateway, CloudFront and IAM roles. - **make**, `git`, `curl`, `unzip`. -- Aurora DSQL, Bedrock etc. beyond the above are **not** used by the bare platform. ## Quick start @@ -94,12 +93,12 @@ make run-local # SPIP on http://localhost:8080 (Apache) | Task | Doc | |---|---| -| Add a plugin (third-party or custom) | [docs/plugins.md](docs/plugins.md) | -| Upgrade SPIP core | [docs/spip-upgrade.md](docs/spip-upgrade.md) | -| Add / configure an environment | [docs/environments.md](docs/environments.md) | -| Initialise the database + admin | [docs/db-bootstrap.md](docs/db-bootstrap.md) | -| Reset an admin password | [docs/spip-passwords.md](docs/spip-passwords.md) | -| DSQL, sessions, S3, secrets, tracing, logging, cron | `docs/*.md` | +| Add a plugin (third-party or custom) | [docs/plugins.md](docs/en/plugins.md) | +| Upgrade SPIP core | [docs/spip-upgrade.md](docs/en/spip-upgrade.md) | +| Add / configure an environment | [docs/environments.md](docs/en/environments.md) | +| Initialise the database + admin | [docs/db-bootstrap.md](docs/en/db-bootstrap.md) | +| Reset an admin password | [docs/spip-passwords.md](docs/en/spip-passwords.md) | +| DSQL, sessions, S3, secrets, tracing, logging, cron | `docs/en/*.md` | ## Security notes diff --git a/README.md b/README.md index 2e71f71..c7487a0 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,11 @@ plugins et vos squelettes. RĂ©gion par dĂ©faut : **eu-west-3** (Paris) đŸ‡«đŸ‡·. - Un compte AWS autorisĂ© Ă  crĂ©er les ressources ci-dessus (Aurora DSQL est disponible en eu-west-3, entre autres). - **Docker** installĂ© et **dĂ©marrĂ©** (buildx ; l'image est `linux/arm64`). -- **Terraform ≄ 1.14** (un vieux 1.5.x dans le PATH Ă©choue sur le contrĂŽle `required_version`). +- **Terraform ≄ 1.14**. - **AWS CLI v2** avec une **session active** pour votre profil — `aws sso login --profile

` ou des credentials exportĂ©s. Le principal doit pouvoir crĂ©er DSQL, S3, ECR, DynamoDB, SSM, Lambda, API Gateway, CloudFront et des rĂŽles IAM. - **make**, `git`, `curl`, `unzip`. -- Au-delĂ , Aurora DSQL, Bedrock, etc. ne sont **pas** utilisĂ©s par la plateforme nue. ## DĂ©marrage rapide @@ -94,14 +93,14 @@ make run-local # SPIP sur http://localhost:8080 (Apache) | TĂąche | Doc | |---|---| -| Ajouter un plugin (tiers ou maison) | [docs/plugins.md](docs/plugins.md) | -| Mettre Ă  jour le cƓur SPIP | [docs/spip-upgrade.md](docs/spip-upgrade.md) | -| Ajouter / configurer un environnement | [docs/environments.md](docs/environments.md) | -| Initialiser la base + l'admin | [docs/db-bootstrap.md](docs/db-bootstrap.md) | -| RĂ©initialiser un mot de passe admin | [docs/spip-passwords.md](docs/spip-passwords.md) | -| DSQL, sessions, S3, secrets, tracing, logs, cron | `docs/*.md` | - -> Les docs dĂ©taillĂ©es sous `docs/*.md` sont en anglais pour l'instant. +| Ajouter un plugin (tiers ou maison) | [docs/plugins.md](docs/fr/plugins.md) | +| Mettre Ă  jour le cƓur SPIP | [docs/spip-upgrade.md](docs/fr/spip-upgrade.md) | +| Ajouter / configurer un environnement | [docs/environments.md](docs/fr/environments.md) | +| Initialiser la base + l'admin | [docs/db-bootstrap.md](docs/fr/db-bootstrap.md) | +| RĂ©initialiser un mot de passe admin | [docs/spip-passwords.md](docs/fr/spip-passwords.md) | +| DSQL, sessions, S3, secrets, tracing, logs, cron | `docs/fr/*.md` | + +> Les docs dĂ©taillĂ©es sous `docs/fr/*.md` sont en anglais pour l'instant. ## Notes de sĂ©curitĂ© diff --git a/docs/cron.md b/docs/en/cron.md similarity index 98% rename from docs/cron.md rename to docs/en/cron.md index 7952caf..626da98 100644 --- a/docs/cron.md +++ b/docs/en/cron.md @@ -1,5 +1,7 @@ # Cron (SPIP Job Queue) +[Français](../fr/cron.md) · **English** + ## Problem SPIP's job queue (`genie`) normally runs inline on every web request — it checks for pending jobs and executes them. On Lambda, this adds latency to every request and is unreliable (Lambda instances are ephemeral). diff --git a/docs/db-bootstrap.md b/docs/en/db-bootstrap.md similarity index 96% rename from docs/db-bootstrap.md rename to docs/en/db-bootstrap.md index 6af969f..3ede0a0 100644 --- a/docs/db-bootstrap.md +++ b/docs/en/db-bootstrap.md @@ -1,5 +1,7 @@ # Bootstrap the DSQL database for a new environment (SPIP) +[Français](../fr/db-bootstrap.md) · **English** + How to **initialise the SPIP schema** (the `spip_*` tables) on an empty Aurora DSQL cluster — once per environment (test / prep / prod). @@ -22,7 +24,7 @@ across accounts, no web wizard, no need to undo the baked `connect.php`. 3. `actualise_plugins_actifs()` + `plugin_installes_meta()` — runs each active plugin's `*_upgrade()` (your plugin migrations, plus any bundled ones) 4. creates the admin author (`0minirezo`, webmestre) with a **peppered** hash via - `Spip\Chiffrer\Password::hacher` (see `docs/spip-passwords.md`) + `Spip\Chiffrer\Password::hacher` (see `docs/en/spip-passwords.md`) ## Run it (per environment) @@ -88,8 +90,8 @@ status done - `spip/scripts/bootstrap-db.php` — the CLI script - `spip/overlay/config/connect.php` — dynamic DSQL connection (IAM token) - `spip/overlay/php/prepend.php` — writes `cles.php` from `SPIP_CLES`, sets `_DIR_*` -- `docs/spip-passwords.md` — peppered admin author hash -- `docs/dsql.md` — psql connection to the cluster via IAM token +- `docs/en/spip-passwords.md` — peppered admin author hash +- `docs/en/dsql.md` — psql connection to the cluster via IAM token ## Technical notes (pitfalls solved) diff --git a/docs/dsql.md b/docs/en/dsql.md similarity index 99% rename from docs/dsql.md rename to docs/en/dsql.md index a9821f7..d33e1aa 100644 --- a/docs/dsql.md +++ b/docs/en/dsql.md @@ -1,5 +1,7 @@ # Aurora DSQL Driver +[Français](../fr/dsql.md) · **English** + ## Overview SPIP uses a custom PostgreSQL driver (`dsql.php`) to connect to Aurora DSQL. It's a fork of SPIP's experimental PostgreSQL driver (`ecrire/req/pg.exp.php`) with DSQL-specific adaptations. diff --git a/docs/environments.md b/docs/en/environments.md similarity index 89% rename from docs/environments.md rename to docs/en/environments.md index ea9c531..b068ad6 100644 --- a/docs/environments.md +++ b/docs/en/environments.md @@ -1,5 +1,7 @@ # Environments +[Français](../fr/environments.md) · **English** + An environment = one AWS account (or one account + region) running its own copy of the two Terraform stacks and the SPIP image. The default region is **eu-west-3** (Paris). @@ -44,7 +46,7 @@ Fill in: > If you change `domain_name` on an already-bootstrapped environment, the runtime host > (prepend.php / `SPIP_PUBLIC_URL`) follows automatically, but the stored `adresse_site` > meta does not — re-run the bootstrap (or `UPDATE spip_meta SET valeur='https://' - > WHERE nom='adresse_site'`). See `docs/db-bootstrap.md`. + > WHERE nom='adresse_site'`). See `docs/en/db-bootstrap.md`. ## First bring-up (per environment) @@ -52,12 +54,12 @@ Fill in: # 1. static stack (DSQL, S3, ECR, DynamoDB, SSM) make deploy-static ENV=prod AWS_PROFILE= -# 2. fill the SPIP key material placeholder in SSM (see docs/db-bootstrap.md) +# 2. fill the SPIP key material placeholder in SSM (see docs/en/db-bootstrap.md) # 3. build + push image, sync assets, apply app stack make deploy ENV=prod AWS_PROFILE= -# 4. initialise the SPIP schema + admin author (see docs/db-bootstrap.md) +# 4. initialise the SPIP schema + admin author (see docs/en/db-bootstrap.md) ``` ## CI/CD @@ -67,4 +69,4 @@ environment under **GitHub → Settings → Environments** with these variables: - `AWS_ACCOUNT_ID`, `AWS_REGION`, `CI_ROLE_NAME` (OIDC role to assume) and commit the matching `var//` files. The workflow assumes an IAM role via GitHub -OIDC — no long-lived keys. +OIDC — no long-lived keys. \ No newline at end of file diff --git a/docs/logging.md b/docs/en/logging.md similarity index 98% rename from docs/logging.md rename to docs/en/logging.md index 69f42af..5ecb394 100644 --- a/docs/logging.md +++ b/docs/en/logging.md @@ -1,5 +1,7 @@ # Logging +[Français](../fr/logging.md) · **English** + ## Architecture SPIP logs → `logs_stderr` plugin → PHP `error_log()` → stderr → CloudWatch Logs diff --git a/docs/plugins.md b/docs/en/plugins.md similarity index 97% rename from docs/plugins.md rename to docs/en/plugins.md index 61ac7ae..5f043b8 100644 --- a/docs/plugins.md +++ b/docs/en/plugins.md @@ -1,5 +1,7 @@ # Managing SPIP plugins on Lambda +[Français](../fr/plugins.md) · **English** + SPIP runs as an **immutable Docker image** here. Plugins are baked into the image at build time — there is no runtime plugin installation. Adding a plugin therefore means: drop it in the repo, add a COPY in the Dockerfile if needed, rebuild, redeploy. @@ -17,7 +19,7 @@ spip/ ``` SPIP core plugins (`plugins-dist/`) are **not** in this repo — they come from the SPIP -core fetched at build time (see `docs/spip-upgrade.md`). A few of them are removed in the +core fetched at build time (see `docs/en/spip-upgrade.md`). A few of them are removed in the Dockerfile (`bigup`, `forum`, `statistiques`, 
) because they don't fit a serverless/ read-mostly deployment. @@ -92,7 +94,7 @@ fetched SPIP core). 3. Data-model migrations go in `_administrations.php` (SPIP's native schema versioning — `spip__metas`/`maj_tables`). They run on the first authenticated - admin visit, or via `spip/scripts/bootstrap-db.php` (see `docs/db-bootstrap.md`). + admin visit, or via `spip/scripts/bootstrap-db.php` (see `docs/en/db-bootstrap.md`). 4. Rebuild + deploy. diff --git a/docs/s3-storage.md b/docs/en/s3-storage.md similarity index 98% rename from docs/s3-storage.md rename to docs/en/s3-storage.md index bcf75b0..a436098 100644 --- a/docs/s3-storage.md +++ b/docs/en/s3-storage.md @@ -1,5 +1,7 @@ # S3 File Storage +[Français](../fr/s3-storage.md) · **English** + ## Problem SPIP stores uploaded files (images, documents) in `IMG/` on the local filesystem. On Lambda, the filesystem is read-only (except `/tmp` which is ephemeral). Files must be stored externally. diff --git a/docs/secrets.md b/docs/en/secrets.md similarity index 98% rename from docs/secrets.md rename to docs/en/secrets.md index bfc623f..17ac47a 100644 --- a/docs/secrets.md +++ b/docs/en/secrets.md @@ -1,5 +1,7 @@ # Secrets Management (SSM Parameter Store) +[Français](../fr/secrets.md) · **English** + ## Architecture ``` diff --git a/docs/sessions.md b/docs/en/sessions.md similarity index 98% rename from docs/sessions.md rename to docs/en/sessions.md index 6183a80..dc84b46 100644 --- a/docs/sessions.md +++ b/docs/en/sessions.md @@ -1,5 +1,7 @@ # Sessions (DynamoDB) +[Français](../fr/sessions.md) · **English** + ## Problem SPIP stores sessions as PHP files in `tmp/sessions/`. On Lambda, `/tmp` is ephemeral and per-instance — sessions are lost on cold starts and not shared across instances. diff --git a/docs/en/spip-passwords.md b/docs/en/spip-passwords.md new file mode 100644 index 0000000..cdf428c --- /dev/null +++ b/docs/en/spip-passwords.md @@ -0,0 +1,106 @@ +# Resetting a SPIP password (SPIP-serverless / DSQL / Lambda) + +[Français](../fr/spip-passwords.md) · **English** + +Procedure to reset a SPIP author's password directly in the database, +when access to the private area is lost. + +## Technical context + +- SPIP runs in **Lambda** (Docker image), database **Aurora DSQL** (PostgreSQL). +- Table prefix: `spip` (SPIP default, configurable via SPIP_TABLE_PREFIX). The authors table is therefore `spip_auteurs`. +- Modern SPIP 4.x does **not** store a plain bcrypt of the password: the hash is + **peppered** with the site's `secret_des_auth` before bcrypt. + +## Hashing formula (SPIP 4.x — `Spip\Chiffrer\Password::hacher`) + +```php +$pass_poivre = hash_hmac('sha256', $password_clair, $key); // $key = secret_des_auth (BINAIRE) +$hash = password_hash($pass_poivre, PASSWORD_DEFAULT); // bcrypt ($2y$
) +``` + +Verification at login (`Password::verifier`) does: + +```php +$pass_poivre = hash_hmac('sha256', $password_clair, $key); +password_verify($pass_poivre, $hash_stocke); +``` + +**Pitfall #1** — `Password::verifier` fails if `$secret` is empty. The secret comes from +the `cles.php` file (populated in the Lambda from the `SPIP_CLES` env var). + +**Pitfall #2 (the most important)** — `SpipCles` **base64-decodes** the keys read from +`cles.php` (`array_map('base64_decode', $json)` in `SpipCles::read()`, and +`base64_decode()` in `getKey`/`getMetaKey`). The HMAC key is therefore the **decoded +binary** secret, NOT the base64 string as it appears in SSM/the JSON. + +## Procedure + +### 1. Retrieve the `secret_des_auth` + +The Lambda's `SPIP_CLES` env var points to an SSM parameter: + +```bash +AWS_PROFILE= aws lambda get-function-configuration \ + --function-name spip-serverless-test-web --region us-east-1 \ + --query "Environment.Variables.SPIP_CLES" --output text +# -> bref-ssm:/spip-serverless/test/spip/cles + +AWS_PROFILE= aws ssm get-parameter \ + --name "/spip-serverless/test/spip/cles" --with-decryption --region us-east-1 \ + --query "Parameter.Value" --output text +# -> {"secret_du_site": "...", "secret_des_auth": ""} +``` + +### 2. Generate the hash (base64-DECODED secret) + +```bash +SECRET_B64='' # secret_des_auth du SSM +NEWPASS='ChangeMe2026!' + +HASH=$(php -r ' +$secret = base64_decode($argv[1]); // IMPORTANT : dĂ©coder le base64 +$poivre = hash_hmac("sha256", $argv[2], $secret); +echo password_hash($poivre, PASSWORD_DEFAULT); +' "$SECRET_B64" "$NEWPASS") + +# VĂ©rifier localement AVANT d'Ă©crire en base +php -r ' +$secret = base64_decode($argv[1]); +$poivre = hash_hmac("sha256", $argv[3], $secret); +echo password_verify($poivre, $argv[2]) ? "VERIFY OK\n" : "VERIFY FAIL\n"; +' "$SECRET_B64" "$HASH" "$NEWPASS" +``` + +### 3. Write to the database (DSQL) + +```bash +TOKEN=$(AWS_PROFILE= aws dsql generate-db-connect-admin-auth-token \ + --hostname .dsql.us-east-1.on.aws --region us-east-1) + +PGPASSWORD="$TOKEN" psql \ + "host=.dsql.us-east-1.on.aws port=5432 dbname=postgres user=admin sslmode=require" \ + -c "UPDATE spip_auteurs SET pass = '$HASH' WHERE id_auteur = 1 RETURNING id_auteur, login, email" +``` + +The current admin author: `id_auteur=1`, login `admin`, email `admin@example.com`, +status `0minirezo`, `webmestre=oui`. + +## Notes / troubleshooting + +- Login can be done with the **login** (`admin`) or the **email**. +- Do NOT touch `alea_actuel` (used to verify old sha256/md5 hashes, with no + impact on modern hashes). +- The repo's `src/
` file must match the SPIP version baked into the Lambda + image; check it in the image if needed: + `docker run --rm --entrypoint sh spip-serverless:latest -c "grep -n hash_hmac /var/task/ecrire/src/Chiffrer/Password.php"` +- **Changing the password from the private area**: may fail in a Lambda + environment (writing the `cles.php` file / backing up the keys on the read-only + `/var/task` filesystem, only `/tmp` is writable). The in-database reset above remains + the reliable method. + +## Relevant SPIP code paths + +- `ecrire/auth/spip.php` — `auth_spip_verifier_pass()` (dispatch based on `strlen($pass)`) +- `ecrire/src/Chiffrer/Password.php` — `hacher()` / `verifier()` +- `ecrire/src/Chiffrer/SpipCles.php` — `getSecretAuth()`, `getKey()`, `read()` (base64_decode) \ No newline at end of file diff --git a/docs/spip-upgrade.md b/docs/en/spip-upgrade.md similarity index 91% rename from docs/spip-upgrade.md rename to docs/en/spip-upgrade.md index 1f8e62a..e776a04 100644 --- a/docs/spip-upgrade.md +++ b/docs/en/spip-upgrade.md @@ -1,5 +1,7 @@ # Upgrading SPIP core +[Français](../fr/spip-upgrade.md) · **English** + SPIP core is **not vendored** in this repo. It is fetched at build time from the official archive (`files.spip.net`) at a single pinned version. Upgrading SPIP is therefore just: **bump the pinned version, rebuild, test on a non-prod environment, promote.** @@ -15,7 +17,7 @@ The image is **immutable and stateless**. Two clean halves: So: **all code/version changes are repo → build → deploy; all data/migration changes are runtime**, triggered by the first admin request (same cold-start mechanism as the initial -plugin migrations — see `docs/db-bootstrap.md`). You never run a DB migration by hand for +plugin migrations — see `docs/en/db-bootstrap.md`). You never run a DB migration by hand for an upgrade; you deploy the image and open the admin once. ## Where the version is pinned @@ -45,7 +47,7 @@ It is consumed by: make deploy ENV=test ``` Check the admin (`/ecrire`), a public page, and login. -5. Commit and let CI promote (see `docs/environments.md`). +5. Commit and let CI promote (see `docs/en/environments.md`). Our customisations live **outside** SPIP core, so a core upgrade never touches them: - `spip/overlay/**` — files that override core at build time (connect.php, mes_options*, @@ -68,5 +70,5 @@ Riskier — core can add/remove files and change APIs: - The core upgrade does **not** replay SPIP's own DB migrations: if core bumps its `spip_version_base`, the SPIP upgrade runs on the first authenticated admin visit (same - cold-start mechanism as plugin migrations — see `docs/db-bootstrap.md`). -- Never commit a downloaded zip or `spip/src/` — both are git-ignored. + cold-start mechanism as plugin migrations — see `docs/en/db-bootstrap.md`). +- Never commit a downloaded zip or `spip/src/` — both are git-ignored. \ No newline at end of file diff --git a/docs/tracing.md b/docs/en/tracing.md similarity index 99% rename from docs/tracing.md rename to docs/en/tracing.md index 6273a55..1135999 100644 --- a/docs/tracing.md +++ b/docs/en/tracing.md @@ -1,5 +1,7 @@ # OpenTelemetry Tracing (X-Ray) +[Français](../fr/tracing.md) · **English** + ## Architecture ``` diff --git a/docs/fr/cron.md b/docs/fr/cron.md new file mode 100644 index 0000000..72dd7bd --- /dev/null +++ b/docs/fr/cron.md @@ -0,0 +1,80 @@ +# Cron (file de jobs SPIP) + +**Français** · [English](../en/cron.md) + +## ProblĂšme + +La file de jobs de SPIP (`genie`) s'exĂ©cute normalement en ligne Ă  chaque requĂȘte web — elle vĂ©rifie la prĂ©sence de jobs en attente et les exĂ©cute. Sur Lambda, cela ajoute de la latence Ă  chaque requĂȘte et n'est pas fiable (les instances Lambda sont Ă©phĂ©mĂšres). + +## Solution + +1. **Bloquer la file sur les requĂȘtes web** — `_DEBUG_BLOCK_QUEUE = true` dans `prepend.php` +2. **DĂ©clencher via EventBridge** — une rĂšgle planifiĂ©e invoque Lambda toutes les 5 minutes avec `?action=cron` + +## Fonctionnement + +### RequĂȘtes web (bloquĂ©es) +`prepend.php` s'exĂ©cute avant SPIP et dĂ©finit : +```php +if (empty($_GET['action']) || $_GET['action'] !== 'cron') { + define('_DEBUG_BLOCK_QUEUE', true); +} +``` +Cela empĂȘche SPIP d'exĂ©cuter le moindre job en attente. Un SELECT lĂ©ger sur `spip_jobs` s'exĂ©cute quand mĂȘme (~3 ms) — c'est SPIP qui vĂ©rifie la prĂ©sence de tĂąches en attente sans les exĂ©cuter. + +### RequĂȘtes cron (EventBridge) +EventBridge invoque Lambda toutes les 5 minutes avec : +```json +{ + "version": "2.0", + "rawPath": "/spip.php", + "rawQueryString": "action=cron", + "queryStringParameters": {"action": "cron"}, + ... +} +``` +Comme `$_GET['action'] === 'cron'`, `_DEBUG_BLOCK_QUEUE` n'est PAS dĂ©fini, et SPIP traite tous les jobs en attente. + +## Ressources Terraform (`iac/spip/app/cron.tf`) + +- `aws_cloudwatch_event_rule.spip_cron` — planification : `rate(5 minutes)` +- `aws_cloudwatch_event_target.spip_cron` — invoque Lambda avec l'Ă©vĂ©nement cron +- `aws_lambda_permission.eventbridge_cron` — autorise EventBridge Ă  invoquer Lambda + +## Types de jobs SPIP + +Jobs courants qui s'exĂ©cutent via le cron : +- `queue_watch` — surveille la file de jobs elle-mĂȘme +- `optimiser` — optimisation de la base de donnĂ©es +- `maintenance` — tĂąches de maintenance gĂ©nĂ©rales +- `mise_a_jour` — vĂ©rifications de mises Ă  jour +- `revisions_optimiser_revisions` — nettoyage de l'historique des rĂ©visions +- `medias_nettoyer_repertoire_upload` — nettoyage du rĂ©pertoire d'upload +- `svp_actualiser_depots` — rafraĂźchissement des infos du dĂ©pĂŽt de plugins + +## Modifier la frĂ©quence + +Modifiez `iac/spip/app/cron.tf` : +```hcl +schedule_expression = "rate(5 minutes)" # Change to "rate(15 minutes)" etc. +``` + +## DĂ©clenchement manuel + +```bash +curl https://cms.example.com/spip.php?action=cron +``` + +## Supervision + +Les invocations du cron apparaissent dans X-Ray sous forme de traces avec : +- DurĂ©e > 3 s (traitement de nombreux jobs) +- 30 Ă  50 requĂȘtes BD +- Aucun segment API Gateway (invocation directe de Lambda depuis EventBridge) + +## Impact sur les requĂȘtes web + +Avant : ~25 requĂȘtes BD par page d'admin, exĂ©cution des jobs comprise +AprĂšs : ~11 requĂȘtes BD par page d'admin (jobs bloquĂ©s) + +Le SELECT restant sur `spip_jobs` (~3 ms) ne peut pas ĂȘtre Ă©liminĂ© sans patcher le cƓur de SPIP (`ecrire/inc/queue.php`). diff --git a/docs/fr/db-bootstrap.md b/docs/fr/db-bootstrap.md new file mode 100644 index 0000000..14d339b --- /dev/null +++ b/docs/fr/db-bootstrap.md @@ -0,0 +1,135 @@ +# Initialiser la base DSQL pour un nouvel environnement (SPIP) + +**Français** · [English](../en/db-bootstrap.md) + +Comment **initialiser le schĂ©ma SPIP** (les tables `spip_*`) sur un cluster Aurora DSQL +vide — une fois par environnement (test / prep / prod). + +MĂ©thode : exĂ©cuter **`spip/scripts/bootstrap-db.php` dans le conteneur**. Reproductible +d'un compte Ă  l'autre, sans assistant web, sans avoir Ă  annuler le `connect.php` intĂ©grĂ©. + +## Pourquoi un script CLI (et non l'assistant / un exec / une route HTTP) + +- `config/connect.php` est intĂ©grĂ© Ă  l'image → SPIP se croit toujours installĂ© et ne + lance jamais l'assistant. +- Le script CLI charge le noyau SPIP (`inc_version.php`) comme le contrĂŽleur frontal, + puis appelle `creer_base()` + les mises Ă  niveau des plugins + la crĂ©ation de l'admin. + Pas de HTTP, pas d'authentification, idempotent. Nul besoin de retirer `connect.php` : + il ne fait que fournir la connexion au cluster, et `creer_base()` y crĂ©e le schĂ©ma quoi + qu'il en soit. + +## Ce que fait le script (idempotent) + +1. `creer_base()` — tables du cƓur SPIP (relance `alterer_base`, tolĂšre « already exists ») +2. metas `version_installee` / `nouvelle_install` (sur une base neuve) +3. `actualise_plugins_actifs()` + `plugin_installes_meta()` — exĂ©cute le `*_upgrade()` de + chaque plugin actif (vos migrations de plugin, plus celles Ă©ventuellement fournies) +4. crĂ©e l'auteur admin (`0minirezo`, webmestre) avec un hash **poivrĂ©** via + `Spip\Chiffrer\Password::hacher` (voir `docs/fr/spip-passwords.md`) + +## L'exĂ©cuter (par environnement) + +Depuis l'**image Lambda** (recommandĂ© : `prepend.php` Ă©crit `cles.php` Ă  partir de +`SPIP_CLES` et positionne les `_DIR_*`, de sorte que le secret d'authentification est +disponible pour hacher le mot de passe admin) : + +```bash +ENV=test ; PROFILE= ; REGION=eu-west-3 +CLUSTER=$(cd iac/spip/static && AWS_PROFILE=$PROFILE terraform output -raw dsql_endpoint) + +# key material (secret_du_site + secret_des_auth) from SSM +SPIP_CLES=$(AWS_PROFILE=$PROFILE aws ssm get-parameter --name /spip-serverless/$ENV/spip/cles \ + --with-decryption --region $REGION --query Parameter.Value --output text) + +# temporary credentials for the target account +eval "$(AWS_PROFILE=$PROFILE aws configure export-credentials --format env)" + +docker run --rm \ + -e SPIP_DSQL_CLUSTER=$CLUSTER \ + -e SPIP_TABLE_PREFIX=spip \ + -e SPIP_PUBLIC_URL=https:// \ + -e SPIP_CLES="$SPIP_CLES" \ + -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN \ + -e AWS_REGION=$REGION \ + --entrypoint php \ + .dkr.ecr.$REGION.amazonaws.com/spip-serverless: \ + -d auto_prepend_file= /var/task/scripts/bootstrap-db.php \ + --admin-login=admin --admin-email=you@example.org --admin-pass='' +``` + +**Important — `-d auto_prepend_file=`** : `prepend.php` (auto_prepend sur Lambda) casse +`inc_version.php` dans un contexte CLI ; le dĂ©sactiver. Le script réécrit lui-mĂȘme +`cles.php` Ă  partir de `$SPIP_CLES` pour que le hash du mot de passe admin fonctionne. + +PrĂ©requis SSM : `/spip-serverless//spip/cles` doit contenir un vrai JSON +`{"secret_du_site":"","secret_des_auth":""}` (la stack statique le crĂ©e +avec un placeholder `CHANGE_ME_AFTER_CREATION` — Ă  renseigner une fois par compte) : + +```bash +SITE=$(openssl rand -base64 32); AUTH=$(openssl rand -base64 32) +AWS_PROFILE=$PROFILE aws ssm put-parameter --name /spip-serverless/$ENV/spip/cles \ + --type SecureString --overwrite --region $REGION \ + --value "$(printf '{"secret_du_site": "%s", "secret_des_auth": "%s"}' "$SITE" "$AUTH")" +``` + +Sortie attendue (rapport ligne par ligne) : +``` +db_connection ok +creer_base ok +metas created +plugins_upgrade ok +admin created (id_auteur=1) +status done +``` + +## Ordre + +1. **test** — exĂ©cuter le script, puis vĂ©rifier `https:///spip.php?page=backend` → 200. +2. **prep**, puis **prod** — mĂȘme script, changer cluster + credentials + SPIP_CLES + admin. + +## Fichiers concernĂ©s + +- `spip/scripts/bootstrap-db.php` — le script CLI +- `spip/overlay/config/connect.php` — connexion DSQL dynamique (jeton IAM) +- `spip/overlay/php/prepend.php` — Ă©crit `cles.php` Ă  partir de `SPIP_CLES`, positionne les `_DIR_*` +- `docs/fr/spip-passwords.md` — hash poivrĂ© de l'auteur admin +- `docs/fr/dsql.md` — connexion psql au cluster via jeton IAM + +## Notes techniques (piĂšges rĂ©solus) + +ExĂ©cuter `creer_base()` depuis la CLI sur une base **vide** a nĂ©cessitĂ© de contourner +plusieurs comportements de SPIP : + +1. **prepend.php casse `inc_version.php` en CLI** → exĂ©cuter avec `php -d auto_prepend_file=`. +2. **`inc_version.php` sort prĂ©maturĂ©ment sans le noyau SpipLeague** → charger d'abord + `vendor/autoload.php` (comme `spip.php`), puis `param('spip.dirs.core')`. +3. **`spip_connect()` Ă©choue sur une base vide** : `spip_connect_main()` lit le charset + depuis `spip_meta` (absent) → renvoie false → chaque `sql_*` rend la page 503 + `MinipageAdmin` dans une **boucle infinie**. Contourner (comme `install/etape_3.php`) + en prĂ©-remplissant la connexion par dĂ©faut Ă  la main. L'index de la connexion par + dĂ©faut est l'entier **`0`** (`$index = $serveur ?: 0`), donc peupler + `$GLOBALS['connexions'][0]` (PAS `['']`) avec la description + le jeu de fonctions + `$GLOBALS['spip_dsql_functions_1']` + prefix/db/version. +4. **`_ECRIRE_INSTALL`** dĂ©fini pour le mode installation. +5. **`cles.php` absent** (auto_prepend dĂ©sactivĂ©) → le script le réécrit Ă  partir de + `SPIP_CLES` dans `_DIR_ETC` pour que `SpipCles::getSecretAuth()` renvoie le secret + d'authentification. + +## PiĂšge SVP / plugin_installes + +La page privĂ©e « Plugins » (`?exec=admin_plugin`) peut se bloquer sur un environnement +fraĂźchement initialisĂ© Ă  cause de **SVP** : `svp_actualiser_paquets_locaux()` fait +`in_array($x, lire_config('plugin_installes'))`, et quand `plugin_installes` est absent +(jamais initialisĂ© par le bootstrap CLI) → `in_array(x, null)` → fatal → page bloquĂ©e. + +Le script s'en prĂ©munit : +- `actualise_plugins_actifs()` (rafraĂźchit la meta `plugin`) +- si `plugin_installes` est absent, l'initialiser Ă  `[]` +- puis `plugin_installes_meta()` — c'est ce que fait la premiĂšre visite admin : il exĂ©cute + l'installation/mise Ă  niveau de chaque plugin actif ET remplit `plugin_installes`. C'est + le dĂ©clencheur fiable pour TOUTES les migrations, contrairement Ă  + `actualise_plugins_actifs()` seul. + +RĂ©paration manuelle (si un env est dĂ©jĂ  bloquĂ©) : forcer en base +`UPDATE spip_meta SET valeur='0.6.2' WHERE nom='svp_base_version';` et insĂ©rer un tableau +`plugin_installes` sĂ©rialisĂ© (le copier depuis un env sain). diff --git a/docs/fr/dsql.md b/docs/fr/dsql.md new file mode 100644 index 0000000..ed06294 --- /dev/null +++ b/docs/fr/dsql.md @@ -0,0 +1,152 @@ +# Pilote Aurora DSQL + +**Français** · [English](../en/dsql.md) + +## Vue d'ensemble + +SPIP utilise un pilote PostgreSQL sur mesure (`dsql.php`) pour se connecter Ă  Aurora DSQL. C'est un fork du pilote PostgreSQL expĂ©rimental de SPIP (`ecrire/req/pg.exp.php`) avec des adaptations spĂ©cifiques Ă  DSQL. + +**Emplacement :** `spip/overlay/ecrire/req/dsql.php` + +## Connexion + +ConfigurĂ©e dans `spip/overlay/config/connect.php` : +- GĂ©nĂšre des jetons d'authentification IAM via `Aws\DSQL\AuthTokenGenerator` +- Se connecte avec `sslmode=require` +- Base de donnĂ©es : `postgres` (valeur par dĂ©faut de DSQL) +- PrĂ©fixe de table : `spip` (configurable via la variable d'environnement `SPIP_TABLE_PREFIX`) + +**Variables d'environnement :** +``` +SPIP_DSQL_CLUSTER=.dsql.eu-west-3.on.aws +SPIP_TABLE_PREFIX=spip +``` + +## DiffĂ©rences clĂ©s par rapport Ă  PostgreSQL standard + +### Colonnes IDENTITY (pas de sĂ©quences) +DSQL utilise `GENERATED BY DEFAULT AS IDENTITY` au lieu de `bigserial`/sĂ©quences. Le `spip_dsql_insert()` du pilote utilise directement `RETURNING ` au lieu de `RETURNING currval('seq')`. + +### CREATE INDEX ASYNC +DSQL requiert `CREATE INDEX ASYNC` au lieu de `CREATE INDEX`. Le pilote effectue la traduction automatiquement. + +### Pas de prise en charge de ALTER TABLE +DSQL a une prise en charge trĂšs limitĂ©e de `ALTER TABLE`. Impossible de faire `DROP NOT NULL`, `SET DEFAULT`, etc. Les modifications de schĂ©ma peuvent nĂ©cessiter la recrĂ©ation de la table. + +### Comparaisons de chaĂźnes sensibles Ă  la casse +DSQL/PostgreSQL est sensible Ă  la casse. Le plugin SVP de SPIP stocke les prĂ©fixes en MAJUSCULES (`OTEL`, `S3UPLOAD`). Les requĂȘtes doivent respecter la casse. + +### Contraintes NOT NULL +Contrairement Ă  MySQL, DSQL applique strictement NOT NULL. Le `spip_dsql_cite()` du pilote renvoie `'0'` au lieu de `NULL` pour les colonnes entiĂšres afin d'Ă©viter les violations de contrainte. + +## Correctifs appliquĂ©s (traduction MySQL → PostgreSQL/DSQL) + +### `spip_dsql_frommysql()` — couche de traduction SQL + +| Syntaxe MySQL | Traduction DSQL | +|---|---| +| `rand()` | `random()` | +| `TIMESTAMPDIFF(HOUR, a, b)` | `EXTRACT(EPOCH FROM (b - a)) / 3600` | +| `UNIX_TIMESTAMP()` | `EXTRACT(epoch FROM NOW())` | +| `DAYOFMONTH(x)` | `EXTRACT(day FROM x)` | +| `md5(integer)` | `MD5(CAST(integer AS text))` | +| `IN ('')` sur des colonnes entiĂšres | `IN (NULL)` | +| `0+column` | `CAST(substring(column, '^ *[0-9]+') as int)` | + +### `spip_dsql_countsel()` — correctif PHP 8.4 +`is_resource($r)` → `is_resource($r) \|\| is_object($r)` car le `pg_query` de PHP 8.4 renvoie un objet `PgSql\Result`, pas une ressource. + +### `spip_dsql_cite()` — gestion des NULL +```php +if (is_null($v)) { + return sql_test_int($t) ? '0' : 'NULL'; +} +``` + +### ChaĂźne vide sur des colonnes entiĂšres +```php +} elseif ($v === '') { + return '0'; +} +``` + +### SELECT DISTINCT + ORDER BY +DSQL exige que les expressions ORDER BY figurent dans la liste SELECT lorsqu'on utilise DISTINCT : +```php +if ($orderby && preg_match('/\bDISTINCT\b/i', $select)) { + // Add ORDER BY expressions (including CASE) to SELECT list +} +``` + +### INSERT RETURNING +Utilise directement le nom de colonne au lieu de `currval('sequence')` : +```php +$seq = spip_dsql_sequence($table, true); // Returns primary key column name +// RETURNING id_plugin (not RETURNING currval('spip_plugins_id_plugin_seq')) +``` + +## Instrumentation OTEL + +Les requĂȘtes en base sont tracĂ©es dans `spip_dsql_trace_query()` : +```php +$connexion['last'] = $query; +if (isset($GLOBALS['_otel_tracer'])) { + $_span = $GLOBALS['_otel_tracer']->spanBuilder('DSQL') + ->setAttribute('db.system', 'postgresql') + ->setAttribute('db.statement', substr($query, 0, 200)) + ->startSpan(); +} +$r = spip_dsql_query_simple($link, $query); +if (isset($_span)) { $_span->end(); unset($_span); } +``` + +## Registre des fonctions + +Le pilote enregistre ces fonctions auprĂšs de la couche d'abstraction SQL de SPIP : + +```php +$GLOBALS['spip_dsql_functions_1'] = [ + 'query', 'countsel', 'select', 'selectdb', 'count', + 'fetch', 'seek', 'free', 'insert', 'insertq', 'insertq_multi', + 'update', 'delete', 'replace', 'multi', + 'error', 'errno', 'explain', + 'optimize', 'repair', 'showtable', 'listdbs', + 'in', 'hex', 'quote', 'date_proche', + 'table_exists', 'set_charset', 'get_charset', +]; +``` + +## DĂ©bogage + +### Activer la journalisation des requĂȘtes +Positionner `LOG_LEVEL=DEBUG` — toutes les requĂȘtes sont journalisĂ©es via `spip_log()` sur le canal `dsql`. + +### Erreurs courantes + +| Erreur | Cause | Correctif | +|---|---|---| +| `null value in column "X" violates not-null constraint` | SPIP passe NULL pour une colonne entiĂšre | CorrigĂ© dans `spip_dsql_cite()` — renvoie '0' | +| `invalid input syntax for type bigint: ""` | ChaĂźne vide dans une clause IN | CorrigĂ© dans `spip_dsql_frommysql()` — `IN('')` → `IN(NULL)` | +| `SELECT DISTINCT, ORDER BY expressions must appear in select list` | SQL strict de DSQL | CorrigĂ© — ajoute l'expression ORDER BY au SELECT | +| `Missing a url function for type propres` | `sql_countsel` renvoie 0 | CorrigĂ© — vĂ©rification `is_resource \|\| is_object` | + +## SchĂ©ma + +Les tables utilisent le prĂ©fixe `spip_` (configurable). Créées lors de l'installation initiale de SPIP via l'assistant. Tables principales : +- `spip_articles`, `spip_rubriques` — contenu +- `spip_auteurs`, `spip_auteurs_liens` — utilisateurs +- `spip_documents`, `spip_documents_liens` — fichiers +- `spip_meta` — magasin de configuration clĂ©-valeur +- `spip_paquets`, `spip_plugins` — registre des plugins +- `spip_jobs`, `spip_jobs_liens` — file de jobs + +## Se connecter manuellement + +```bash +TOKEN=$(aws dsql generate-db-connect-admin-auth-token \ + --hostname .dsql.eu-west-3.on.aws \ + --expires-in 3600 --profile --region us-east-1) +PGPASSWORD="$TOKEN" psql \ + -h .dsql.eu-west-3.on.aws \ + -U admin -d postgres +``` diff --git a/docs/fr/environments.md b/docs/fr/environments.md new file mode 100644 index 0000000..ffc4800 --- /dev/null +++ b/docs/fr/environments.md @@ -0,0 +1,73 @@ +# Environnements + +**Français** · [English](../en/environments.md) + +Un environnement = un compte AWS (ou un compte + une rĂ©gion) faisant tourner sa propre copie +des deux stacks Terraform et de l'image SPIP. La rĂ©gion par dĂ©faut est **eu-west-3** (Paris). + +## Organisation Terraform + +``` +iac/spip/ +├── static/ # DSQL cluster, S3 assets bucket, ECR repo, DynamoDB sessions, SSM key +└── app/ # Lambda, API Gateway, CloudFront (+ optional custom domain) +``` + +`app` lit les outputs de `static` via `terraform_remote_state`, donc **appliquez `static` +en premier**. + +## Fichiers par environnement + +Chaque stack possĂšde `var//` : +- `values.tfvars` — rĂ©gion, nom d'env, domaine, emplacement du remote-state, 
 +- `backend.tfbackend` — oĂč vit l'Ă©tat PROPRE Ă  CE stack (bucket S3 + clĂ©) + +Un modĂšle se trouve dans `var/example/`. CrĂ©ez un nouvel environnement en le copiant : + +```bash +for stack in static app; do + cp -r iac/spip/$stack/var/example iac/spip/$stack/var/prod + $EDITOR iac/spip/$stack/var/prod/values.tfvars + $EDITOR iac/spip/$stack/var/prod/backend.tfbackend +done +``` + +Renseignez : +- `aws_region` — p. ex. `eu-west-3` +- le bucket S3 d'Ă©tat dans les deux fichiers `backend.tfbackend` (Ă  crĂ©er une fois par compte) +- `static_state_bucket` / `static_state_region` dans le stack app (pointer vers l'Ă©tat du + stack static) +- optionnellement `domain_name` + `hosted_zone_name` pour un domaine personnalisĂ©. DĂ©finir + `domain_name` (via Terraform, pas la console CloudFront) pilote tout depuis une seule + variable : l'alias CloudFront, un certificat ACM en us-east-1 (validĂ© par DNS contre la + zone Route53 `hosted_zone_name`, qui doit exister **dans le mĂȘme compte**), l'enregistrement + A Route53, **et** le `SPIP_PUBLIC_URL` du Lambda (afin que SPIP construise ses liens absolus + sur ce domaine). Laissez les deux vides pour utiliser le domaine `*.cloudfront.net` par dĂ©faut. + + > Si vous changez `domain_name` sur un environnement dĂ©jĂ  amorcĂ© (bootstrapped), l'hĂŽte + > runtime (prepend.php / `SPIP_PUBLIC_URL`) suit automatiquement, mais la mĂ©ta `adresse_site` + > stockĂ©e ne suit pas — relancez le bootstrap (ou `UPDATE spip_meta SET valeur='https://' + > WHERE nom='adresse_site'`). Voir `docs/fr/db-bootstrap.md`. + +## PremiĂšre mise en route (par environnement) + +```bash +# 1. static stack (DSQL, S3, ECR, DynamoDB, SSM) +make deploy-static ENV=prod AWS_PROFILE= + +# 2. fill the SPIP key material placeholder in SSM (see docs/fr/db-bootstrap.md) + +# 3. build + push image, sync assets, apply app stack +make deploy ENV=prod AWS_PROFILE= + +# 4. initialise the SPIP schema + admin author (see docs/fr/db-bootstrap.md) +``` + +## CI/CD + +`.github/workflows/deploy.yml` dĂ©ploie un environnement par exĂ©cution. Configurez chaque +environnement sous **GitHub → Settings → Environments** avec ces variables : +- `AWS_ACCOUNT_ID`, `AWS_REGION`, `CI_ROLE_NAME` (rĂŽle OIDC Ă  assumer) + +et committez les fichiers `var//` correspondants. Le workflow assume un rĂŽle IAM via +GitHub OIDC — pas de clĂ©s Ă  longue durĂ©e de vie. \ No newline at end of file diff --git a/docs/fr/logging.md b/docs/fr/logging.md new file mode 100644 index 0000000..9470f4c --- /dev/null +++ b/docs/fr/logging.md @@ -0,0 +1,112 @@ +# Journalisation + +**Français** · [English](../en/logging.md) + +## Architecture + +Logs SPIP → plugin `logs_stderr` → PHP `error_log()` → stderr → CloudWatch Logs + +Le plugin `logs_stderr` (`spip/plugins-vendor/logs_stderr/`) remplace la fonction `inc_log()` de SPIP pour Ă©crire vers stderr au lieu des fichiers `tmp/log/`. Sur Lambda, stderr part directement vers CloudWatch. + +## Configuration + +Variables d'environnement sur Lambda (dĂ©finies dans `iac/spip/app/locals.tf`) : + +| Variable | Valeurs | DĂ©faut | Description | +|---|---|---|---| +| `LOG_LEVEL` | `ERROR`, `WARNING`, `INFO`, `DEBUG` | `WARNING` | Niveau minimal Ă  Ă©mettre | +| `LOG_FORMAT` | `json`, `text` | `json` | Format de sortie | + +## Format de sortie + +### JSON (par dĂ©faut) +```json +{"level":"ERROR","channel":"dsql","context":"prive","message":"errcode: 1000 : ..."} +``` + +Champs : +- `level` — ERROR, WARNING, INFO, DEBUG +- `channel` — canal de log (spip, dsql, base, etc.) +- `context` — `prive` (admin) ou `public` +- `message` — message de log + +### Texte +``` +[spip][ERROR][dsql][prive][pid:5] errcode: 1000 : ... +``` + +## Filtrage par niveau + +Le plugin extrait le niveau Ă  partir du prĂ©fixe du message de log SPIP : +- `ERREUR:`, `ERROR:`, `HS:` → ERROR +- `WARNING:`, `AVERTISSEMENT:` → WARNING +- `INFO:`, `!INFO:` → INFO +- Tout le reste → INFO + +Les messages en dessous de `LOG_LEVEL` sont Ă©cartĂ©s. + +## RequĂȘtes CloudWatch Logs Insights + +### Toutes les erreurs de la derniĂšre heure +``` +fields @timestamp, @message +| filter @message like /\"level\":\"ERROR\"/ +| sort @timestamp desc +| limit 50 +``` + +### Erreurs BD +``` +fields @timestamp, @message +| filter @message like /\"channel\":\"dsql\"/ +| sort @timestamp desc +``` + +### Analyse de motifs +``` +fields @timestamp, @message +| filter @message like /spip/ +| pattern @message +``` + +## Emplacement du plugin + +`spip/plugins-vendor/logs_stderr/` — depuis https://git.spip.net/spip-contrib-extensions/logs_stderr + +Fichier clĂ© : `inc/log.php` — remplace `inc_log()` (la fonction de journalisation de SPIP). + +## Erreurs PHP + +Les erreurs PHP (Fatal, Warning, Notice) partent vers stderr indĂ©pendamment du plugin de log SPIP — elles sont contrĂŽlĂ©es par `error_reporting` dans le PHP ini. Actuellement toutes les erreurs PHP sont journalisĂ©es. Pour supprimer les avertissements, ajoutez au Dockerfile : +```dockerfile +RUN printf '...\nerror_reporting=E_ALL & ~E_WARNING & ~E_NOTICE\n' > /opt/bref/etc/php/conf.d/spip-lambda.ini +``` + +## Journalisation du pilote DSQL + +Le pilote DSQL (`spip/overlay/ecrire/req/dsql.php`) journalise les erreurs SQL via `spip_log()` sur le canal `dsql`. Elles apparaissent ainsi : +```json +{"level":"ERROR","channel":"dsql","context":"prive","message":"errcode: 1000 : ","aws.xray.trace_id":"1-abc123-def456@span123"} +``` + +## CorrĂ©lation avec les traces X-Ray + +Les entrĂ©es de log incluent automatiquement `aws.xray.trace_id` lorsqu'une trace est active. Format : `@`. + +```json +{"level":"HS","channel":"spip","context":"public","message":"...","aws.xray.trace_id":"1-69fc9929-52095a885168a76063ddeaf6@f725f76da07265d7"} +``` + +Cela permet : +- **Trace → Logs :** dans la console X-Ray, cliquez sur une trace pour voir les entrĂ©es de log associĂ©es +- **Logs → Trace :** dans CloudWatch Logs, cliquez sur le lien de l'identifiant de trace pour sauter vers la trace + +### Fonctionnement +Le plugin `logs_stderr` vĂ©rifie la prĂ©sence de `$GLOBALS['_otel_root_span']` (dĂ©fini par la configuration OTEL de `prepend.php`). S'il est prĂ©sent, il extrait l'identifiant de trace et l'identifiant de span et les ajoute Ă  la sortie JSON. + +### RequĂȘte CloudWatch Insights (trouver les logs d'une trace) +``` +fields @timestamp, @message +| filter @message like "1-69fc9929-52095a885168a76063ddeaf6" +| sort @timestamp asc +``` diff --git a/docs/fr/plugins.md b/docs/fr/plugins.md new file mode 100644 index 0000000..021d16c --- /dev/null +++ b/docs/fr/plugins.md @@ -0,0 +1,140 @@ +# GĂ©rer les plugins SPIP sur Lambda + +**Français** · [English](../en/plugins.md) + +SPIP tourne ici sous forme d'**image Docker immuable**. Les plugins sont intĂ©grĂ©s Ă  l'image +au moment du build — il n'y a pas d'installation de plugin Ă  l'exĂ©cution. Ajouter un plugin +signifie donc : le dĂ©poser dans le dĂ©pĂŽt, ajouter un COPY dans le Dockerfile si nĂ©cessaire, +reconstruire, redĂ©ployer. + +## OĂč vivent les plugins dans ce dĂ©pĂŽt + +``` +spip/ +├── plugins/ # OUR custom plugins (source of truth) +│ ├── s3upload/ # presigned-URL uploads to S3 +│ └── sessions_dynamodb/ # DynamoDB session storage (loaded via a squelettes override) +├── plugins-vendor/ # third-party plugins vendored into the repo +│ └── logs_stderr/ # redirect spip_log() to stderr → CloudWatch +└── Dockerfile # COPY-s the above into the image +``` + +Les plugins du cƓur SPIP (`plugins-dist/`) ne sont **pas** dans ce dĂ©pĂŽt — ils proviennent +du cƓur SPIP rĂ©cupĂ©rĂ© au moment du build (voir `docs/fr/spip-upgrade.md`). Quelques-uns sont +retirĂ©s dans le Dockerfile (`bigup`, `forum`, `statistiques`, 
) car ils ne conviennent pas +Ă  un dĂ©ploiement serverless/majoritairement en lecture. + +## Comment l'image mappe les dossiers vers SPIP + +| Source | Chemin dans l'image | Activation | +|---|---|---| +| `plugins-dist/` rĂ©cupĂ©rĂ© (cƓur SPIP) | `/var/task/plugins-dist/` | toujours actif | +| `spip/plugins-vendor/*` | `/var/task/plugins-dist/*` | toujours actif | +| `spip/plugins/s3upload/` | `/var/task/plugins-dist/s3upload/` | toujours actif | +| `spip/plugins/sessions_dynamodb/` | via `squelettes/inc/session.php` | surcharge, pas un plugin | + +Tout ce qui est placĂ© sous `plugins-dist/` est scannĂ© et activĂ© par SPIP au dĂ©marrage Ă  +froid ; SVP l'enregistre en base (`spip_paquets` avec `actif='oui'`) et cĂąble +automatiquement les dĂ©clarations de pipeline du `paquet.xml`. + +### `plugins/` vs `plugins-dist/` — pourquoi la distinction disparaĂźt Ă  l'exĂ©cution + +Dans une installation SPIP **normale**, les deux rĂ©pertoires ont des sens diffĂ©rents : + +- `plugins-dist/` — plugins **livrĂ©s avec le cƓur SPIP**, toujours actifs, sans Ă©tape d'activation. +- `plugins/` — plugins **que vous avez ajoutĂ©s**, qui doivent ĂȘtre **activĂ©s** (enregistrĂ©s en + base via SVP, normalement en cliquant sur « activer » dans l'espace privĂ©). + +Cette Ă©tape d'activation est interactive et Ă©crit sur le disque — ni l'un ni l'autre n'est +possible sur une **Lambda immuable et en lecture seule** qui redescend Ă  zĂ©ro. Ce build +**copie donc dĂ©libĂ©rĂ©ment nos propres plugins dans `plugins-dist/`** (voir le Dockerfile), +oĂč ils sont toujours actifs dĂšs le premier dĂ©marrage Ă  froid, sans Ă©tape manuelle. + +Par consĂ©quent, Ă  l'**exĂ©cution** la distinction `plugins/` vs `plugins-dist/` n'existe +plus — tout vit dans `/var/task/plugins-dist/`. Dans le **dĂ©pĂŽt**, les dossiers sources +sĂ©parĂ©s (`spip/plugins/` = les nĂŽtres, `spip/plugins-vendor/` = tiers) ne sont conservĂ©s +que pour l'organisation et la provenance ; il n'y a pas de `spip/plugins-dist/` (ce nom +appartient au cƓur SPIP rĂ©cupĂ©rĂ©). + +## Ajouter un plugin TIERS + +1. TĂ©lĂ©charger le plugin dans `spip/plugins-vendor//` (il doit avoir un + `paquet.xml` valide). +2. Rien d'autre Ă  changer — le Dockerfile fait dĂ©jĂ  + `COPY spip/plugins-vendor/ /var/task/plugins-dist/`. +3. Reconstruire + dĂ©ployer. Le plugin est actif au prochain dĂ©marrage Ă  froid. + +> Épinglez la version du plugin (committez la copie vendorisĂ©e) pour que les builds +> restent reproductibles. + +## Ajouter un plugin PERSONNALISÉ (le vĂŽtre) + +1. CrĂ©er `spip/plugins//` avec au moins un `paquet.xml` : + + ```xml + + My Plugin + Your name + GPL + + + ``` + +2. Ajouter les lignes COPY dans `spip/Dockerfile` (dans l'Ă©tape `lambda`), Ă  cĂŽtĂ© de celle + de s3upload : + + ```dockerfile + COPY spip/plugins// /var/task/plugins-dist// + ``` + et l'ajouter Ă  la ligne `rm -rf /var/task/plugins/...` pour que le doublon sous + `plugins/` ne soit pas embarquĂ©. + +3. Les migrations du modĂšle de donnĂ©es vont dans `_administrations.php` (le + versionnage de schĂ©ma natif de SPIP — `spip__metas`/`maj_tables`). Elles + s'exĂ©cutent Ă  la premiĂšre visite admin authentifiĂ©e, ou via + `spip/scripts/bootstrap-db.php` (voir `docs/fr/db-bootstrap.md`). + +4. Reconstruire + dĂ©ployer. + +## Exposer une API REST depuis un plugin + +Le contrĂŽleur frontal Lambda (`spip/overlay/router.php`) relaie tout vers SPIP. Pour servir +une API personnalisĂ©e sous, par exemple, `/api/*`, ajouter une branche **avant** celle de +`/ecrire` qui require le point d'entrĂ©e de votre plugin — le routeur documente dĂ©jĂ  le +motif dans un commentaire. Ajoutez ensuite un comportement CloudFront correspondant dans +`iac/spip/app/cloudfront.tf` si vous voulez un cache par chemin. + +## Ordre de chargement des plugins (Lambda) + +1. `auto_prepend_file` → `prepend.php` (wrapper de flux S3, OTEL, rĂ©pertoires tmp) +2. bootstrap SPIP → `ecrire/inc/utils.php` +3. `config/mes_options.php` +4. fichiers `_options.php` des plugins (depuis le cache de plugins SPIP dans `/tmp`) +5. fichiers `_fonctions.php` des plugins +6. exĂ©cution des pipelines + +## Cas particuliers dans ce dĂ©pĂŽt + +### sessions_dynamodb +Surcharge `ecrire_fichier()`/`lire_fichier()` pour les fichiers de session, ce qui entre en +conflit avec le cƓur lorsqu'il est chargĂ© comme un plugin normal. ChargĂ© via une surcharge +de chemin squelettes Ă  la place : +```dockerfile +COPY spip/plugins/sessions_dynamodb/inc/session.php /var/task/squelettes/inc/session.php +``` + +### s3upload +- JS servi depuis S3 Ă  `/plugins-dist/s3upload/s3upload.js` +- le pipeline `header_prive` injecte la balise `