Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
117 changes: 117 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# spip-serverless

[Français](README.md) · **English**

Run [SPIP](https://www.spip.net/) — the French free-software CMS — **serverless on AWS**:
PHP on AWS Lambda (via [Bref](https://bref.sh/)), an Aurora DSQL database, sessions in
DynamoDB, static assets on S3, all fronted by CloudFront. No servers to patch, scale to
zero, pay per request.

This repo is a **generic, reusable platform** — bring your own content, plugins and
skeletons. Default region: **eu-west-3** (Paris) 🇫🇷.

## Architecture

```
┌────────────┐
visitor ──────▶│ CloudFront │
└─────┬──────┘
/IMG,/plugins-dist,… everything else
│ │
┌─────▼─────┐ ┌─────▼───────────┐
│ S3 assets │ │ API Gateway │
└───────────┘ │ (proxy, X-Ray) │
└─────┬───────────┘
│ AWS_PROXY
┌─────▼───────────┐ IAM-auth token
│ Lambda (SPIP) │──────────────▶ Aurora DSQL
│ Bref, PHP 8.5 │──────────────▶ DynamoDB (sessions)
└─────┬───────────┘──────────────▶ S3 (media, read/write)
SSM (spip keys), CloudWatch, X-Ray
```

- **`spip/`** — the SPIP runtime image. SPIP core is **fetched at build time** (pinned in
`spip/SPIP_VERSION`), not vendored. Overlays adapt core to Lambda/DSQL; two custom
plugins (`s3upload`, `sessions_dynamodb`) and `logs_stderr` are baked in.
- **`iac/spip/static/`** — Terraform: DSQL cluster, S3 bucket, ECR repo, DynamoDB table,
SSM key parameter.
- **`iac/spip/app/`** — Terraform: Lambda + alias, API Gateway (transparent proxy),
CloudFront (+ optional custom domain via ACM/Route53).
- **`docs/`** — how everything fits together.

## Prerequisites

- 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).
- **AWS CLI v2** with an **active session** for your profile — `aws sso login --profile <p>`
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

```bash
# 0. pick an environment name + AWS profile
export ENV=test AWS_PROFILE=your-profile

# 1. create the S3 bucket that will hold Terraform state (once per account)
aws s3 mb s3://your-tfstate-bucket --region eu-west-3

# 2. create per-env config from the template
for s in static app; do cp -r iac/spip/$s/var/example iac/spip/$s/var/$ENV; done
# then edit iac/spip/*/var/$ENV/{values.tfvars,backend.tfbackend}:
# set the state bucket (both backend.tfbackend + app's static_state_bucket),
# region, and optionally a custom domain.

# 3. base infra (DSQL, S3, ECR, DynamoDB, SSM)
make deploy-static ENV=$ENV

# 4. fill the SPIP key material in SSM → see docs/db-bootstrap.md

# 5. build + push image, sync assets, deploy the app stack
make deploy ENV=$ENV

# 6. create the schema + admin author → see docs/db-bootstrap.md
```

`make deploy` prints the CloudFront URL; the admin is at `/ecrire`. Verified end-to-end
on a fresh AWS account (eu-west-3): CloudFront serves the public site and `/spip.php?page=login`.

## Working with SPIP core locally

Core is git-ignored. To index it in your IDE or run it with Apache locally:

```bash
make fetch-spip # downloads the pinned version into spip/src/
make run-local # SPIP on http://localhost:8080 (Apache)
```

## Common tasks

| 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` |

## Security notes

- API Gateway is a **transparent proxy** — it does not authenticate. **SPIP** enforces
auth for the back-office (`/ecrire`). If you add your own API routes, add an authorizer
in `iac/spip/app/` yourself.
- SPIP secret keys live in SSM (SecureString) and are written back by the Lambda on first
boot; the S3 bucket is private (CloudFront OAC only); DSQL uses short-lived IAM auth
tokens (no stored DB password).
- You can turn the public site off (admin/login only) with `spip_public_disabled = true`.

## License

The SPIP core fetched at build time is GPL-3.0 (© the SPIP community). This repo's own
glue code is provided under the terms in [LICENSE](LICENSE).
151 changes: 82 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,119 @@
# spip-serverless

Run [SPIP](https://www.spip.net/) — the French free-software CMS — **serverless on AWS**:
PHP on AWS Lambda (via [Bref](https://bref.sh/)), an Aurora DSQL database, sessions in
DynamoDB, static assets on S3, all fronted by CloudFront. No servers to patch, scale to
zero, pay per request.
**Français** · [English](README.en.md)

This repo is a **generic, reusable platform** — bring your own content, plugins and
skeletons. Default region: **eu-west-3** (Paris) 🇫🇷.
Faire tourner [SPIP](https://www.spip.net/) — le CMS libre français — **serverless sur AWS** :
PHP sur AWS Lambda (via [Bref](https://bref.sh/)), une base Aurora DSQL, les sessions dans
DynamoDB, les assets statiques sur S3, le tout derrière CloudFront. Aucun serveur à patcher,
scale-to-zero, facturation à l'usage.

Ce dépôt est une **plateforme générique et réutilisable** — apportez votre contenu, vos
plugins et vos squelettes. Région par défaut : **eu-west-3** (Paris) 🇫🇷.

## Architecture

```
┌────────────┐
visitor ──────▶│ CloudFront │
visiteur ─────▶│ CloudFront │
└─────┬──────┘
/IMG,/plugins-dist,… everything else
│ │
┌─────▼─────┐ ┌────────────────┐
S3 assets │ │ API Gateway │
/IMG,/plugins-dist,… tout le reste
┌─────▼─────┐ ┌────────────────┐
│ assets S3 │ │ API Gateway │
└───────────┘ │ (proxy, X-Ray) │
└─────┬───────────┘
│ AWS_PROXY
┌─────▼───────────┐ IAM-auth token
┌─────▼───────────┐ jeton IAM
│ Lambda (SPIP) │──────────────▶ Aurora DSQL
│ Bref, PHP 8.5 │──────────────▶ DynamoDB (sessions)
└─────┬───────────┘──────────────▶ S3 (media, read/write)
└─────┬───────────┘──────────────▶ S3 (média, lecture/écriture)
SSM (spip keys), CloudWatch, X-Ray
SSM (clés spip), CloudWatch, X-Ray
```

- **`spip/`** — the SPIP runtime image. SPIP core is **fetched at build time** (pinned in
`spip/SPIP_VERSION`), not vendored. Overlays adapt core to Lambda/DSQL; two custom
plugins (`s3upload`, `sessions_dynamodb`) and `logs_stderr` are baked in.
- **`iac/spip/static/`** — Terraform: DSQL cluster, S3 bucket, ECR repo, DynamoDB table,
SSM key parameter.
- **`iac/spip/app/`** — Terraform: Lambda + alias, API Gateway (transparent proxy),
CloudFront (+ optional custom domain via ACM/Route53).
- **`docs/`** — how everything fits together.

## Prerequisites

- An AWS account with permission to create the resources above (Aurora DSQL is available
in eu-west-3 among others).
- Docker (buildx, ARM64), Terraform ≥ 1.14, the AWS CLI, and `make`.
- Aurora DSQL, Bedrock etc. are **not** used by the bare platform — only DSQL, S3,
DynamoDB, Lambda, API Gateway, CloudFront, ECR, SSM.

## Quick start
- **`spip/`** — l'image runtime SPIP. Le cœur SPIP est **récupéré au build** (épinglé dans
`spip/SPIP_VERSION`), non vendorisé. Des overlays adaptent le cœur à Lambda/DSQL ; deux
plugins maison (`s3upload`, `sessions_dynamodb`) et `logs_stderr` sont embarqués.
- **`iac/spip/static/`** — Terraform : cluster DSQL, bucket S3, dépôt ECR, table DynamoDB,
paramètre SSM (clés).
- **`iac/spip/app/`** — Terraform : Lambda + alias, API Gateway (proxy transparent),
CloudFront (+ domaine custom optionnel via ACM/Route53).
- **`docs/`** — comment tout s'articule.

## Prérequis

- 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`).
- **AWS CLI v2** avec une **session active** pour votre profil — `aws sso login --profile <p>`
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

```bash
# 0. pick an environment name + AWS profile
export ENV=test AWS_PROFILE=your-profile
# 0. choisir un nom d'environnement + un profil AWS
export ENV=test AWS_PROFILE=votre-profil

# 1. créer le bucket S3 qui contiendra l'état Terraform (une fois par compte)
aws s3 mb s3://votre-bucket-tfstate --region eu-west-3

# 1. create per-env config from the template
# 2. créer la config par env depuis le modèle
for s in static app; do cp -r iac/spip/$s/var/example iac/spip/$s/var/$ENV; done
# then edit iac/spip/*/var/$ENV/{values.tfvars,backend.tfbackend}
# (state bucket, region, optional domain)
# puis éditer iac/spip/*/var/$ENV/{values.tfvars,backend.tfbackend} :
# renseigner le bucket d'état (les deux backend.tfbackend + static_state_bucket de app),
# la région, et éventuellement un domaine custom.

# 2. base infra (DSQL, S3, ECR, DynamoDB, SSM)
# 3. infra de base (DSQL, S3, ECR, DynamoDB, SSM)
make deploy-static ENV=$ENV

# 3. fill the SPIP key material in SSM → see docs/db-bootstrap.md
# 4. remplir les clés SPIP dans SSM → voir docs/db-bootstrap.md

# 4. build + push image, sync assets, deploy the app stack
# 5. build + push de l'image, sync des assets, déploiement de la stack app
make deploy ENV=$ENV

# 5. create the schema + admin author → see docs/db-bootstrap.md
# 6. créer le schéma + l'auteur admin → voir docs/db-bootstrap.md
```

`make deploy` prints the CloudFront URL; the admin is at `/ecrire`.
`make deploy` affiche l'URL CloudFront ; l'admin est sur `/ecrire`. Vérifié de bout en bout
sur un compte AWS vierge (eu-west-3) : CloudFront sert le site public et `/spip.php?page=login`.

## Working with SPIP core locally
## Travailler avec le cœur SPIP en local

Core is git-ignored. To index it in your IDE or run it with Apache locally:
Le cœur est git-ignoré. Pour l'indexer dans votre IDE ou le lancer avec Apache en local :

```bash
make fetch-spip # downloads the pinned version into spip/src/
make run-local # SPIP on http://localhost:8080 (Apache)
make fetch-spip # télécharge la version épinglée dans spip/src/
make run-local # SPIP sur http://localhost:8080 (Apache)
```

## Common tasks
## Tâches courantes

| Task | Doc |
| Tâche | 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` |

## Security notes

- API Gateway is a **transparent proxy** — it does not authenticate. **SPIP** enforces
auth for the back-office (`/ecrire`). If you add your own API routes, add an authorizer
in `iac/spip/app/` yourself.
- SPIP secret keys live in SSM (SecureString) and are written back by the Lambda on first
boot; the S3 bucket is private (CloudFront OAC only); DSQL uses short-lived IAM auth
tokens (no stored DB password).
- You can turn the public site off (admin/login only) with `spip_public_disabled = true`.

## License

The SPIP core fetched at build time is GPL-3.0 (© the SPIP community). This repo's own
glue code is provided under the terms in [LICENSE](LICENSE).
| 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.

## Notes de sécurité

- L'API Gateway est un **proxy transparent** — elle n'authentifie pas. C'est **SPIP** qui
gère l'auth du back-office (`/ecrire`). Si vous ajoutez vos propres routes API, ajoutez
vous-même un authorizer dans `iac/spip/app/`.
- Les clés secrètes SPIP vivent dans SSM (SecureString) et sont réécrites par le Lambda au
premier boot ; le bucket S3 est privé (OAC CloudFront uniquement) ; DSQL utilise des jetons
IAM à courte durée (aucun mot de passe DB stocké).
- Vous pouvez couper le site public (admin/login seulement) avec `spip_public_disabled = true`.

## Licence

Le cœur SPIP récupéré au build est en GPL-3.0 (© la communauté SPIP). Le code de glue propre
à ce dépôt est fourni selon les termes du fichier [LICENSE](LICENSE).
3 changes: 2 additions & 1 deletion docs/db-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ 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://<cloudfront-or-custom-domain> \
-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 \
<account>.dkr.ecr.$REGION.amazonaws.com/spip-serverless:<tag> \
php -d auto_prepend_file= /var/task/scripts/bootstrap-db.php \
-d auto_prepend_file= /var/task/scripts/bootstrap-db.php \
--admin-login=admin --admin-email=you@example.org --admin-pass='<pass>'
```

Expand Down
13 changes: 11 additions & 2 deletions docs/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ Fill in:
- the state S3 bucket in both `backend.tfbackend` files (create it once per account)
- `static_state_bucket` / `static_state_region` in the app stack (point at the static
stack's state)
- optionally `domain_name` + `hosted_zone_name` for a custom domain (Route53 zone must
exist in the same account)
- optionally `domain_name` + `hosted_zone_name` for a custom domain. Setting
`domain_name` (via Terraform, not the CloudFront console) drives everything from one
variable: the CloudFront alias, a us-east-1 ACM certificate (DNS-validated against the
`hosted_zone_name` Route53 zone, which must exist **in the same account**), the Route53
A-record, **and** the Lambda's `SPIP_PUBLIC_URL` (so SPIP builds its absolute links on
that domain). Leave both empty to use the default `*.cloudfront.net` domain.

> 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://<new>'
> WHERE nom='adresse_site'`). See `docs/db-bootstrap.md`.

## First bring-up (per environment)

Expand Down
19 changes: 19 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ Everything placed under `plugins-dist/` is scanned and activated by SPIP on cold
SVP registers it in the DB (`spip_paquets` with `actif='oui'`) and wires the
`paquet.xml` pipeline declarations automatically.

### `plugins/` vs `plugins-dist/` — why the distinction disappears at runtime

In a **normal** SPIP install the two directories mean different things:

- `plugins-dist/` — plugins **shipped with the SPIP core**, always active, no activation step.
- `plugins/` — plugins **you added**, which must be **activated** (registered in the DB via
SVP, normally by clicking "activate" in the private area).

That activation step is interactive and writes to disk — neither is possible on an
**immutable, read-only Lambda** that scales to zero. So this build deliberately **copies our
own plugins into `plugins-dist/`** (see the Dockerfile), where they are always active from
the first cold start, with no manual step.

Consequently, at **runtime** the `plugins/` vs `plugins-dist/` distinction no longer exists —
everything lives in `/var/task/plugins-dist/`. In the **repo**, the separate source folders
(`spip/plugins/` = ours, `spip/plugins-vendor/` = third-party) are kept only for
organisation and provenance; there is no `spip/plugins-dist/` (that name belongs to the
fetched SPIP core).

## Add a THIRD-PARTY plugin

1. Download the plugin into `spip/plugins-vendor/<plugin-name>/` (it must have a valid
Expand Down
Loading
Loading