feat: standalone libreshop demo stack pinned to v0.1.0
White-label preview: brings up cms + cms-db + mail + pdf + nginx + shop with placeholder env so the toolkit runs clickably without any adapter. Adapters (e.g. mp) replace compose.yaml with their own composition + branding env. Every libreshop component image is pinned to :v0.1.0 — override per service via LIBRESHOP_<SVC>_TAG in .env if testing rolling :main. .env.example documents every variable; mail stays in stdout-log mode and PayPal stays in sandbox/no-creds mode by default.
This commit is contained in:
42
.env.example
Normal file
42
.env.example
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# libreshop/demo — copy to .env and edit. Defaults below are
|
||||||
|
# placeholder values; the stack boots clickably with them but mail
|
||||||
|
# stays in stdout-log mode and PayPal stays in sandbox/no-creds mode.
|
||||||
|
|
||||||
|
# Pinned versions (override per service if you want to test :main).
|
||||||
|
LIBRESHOP_CMS_TAG=v0.1.0
|
||||||
|
LIBRESHOP_MAIL_TAG=v0.1.0
|
||||||
|
LIBRESHOP_PDF_TAG=v0.1.0
|
||||||
|
LIBRESHOP_SHOP_TAG=v0.1.0
|
||||||
|
LIBRESHOP_NGINX_TAG=v0.1.0
|
||||||
|
|
||||||
|
# Where the demo binds nginx (host port).
|
||||||
|
LIBRESHOP_DEMO_PORT=8080
|
||||||
|
|
||||||
|
# Strapi DB (local-only credentials; rotate for any non-toy run).
|
||||||
|
CMS_DB_NAME=libreshop
|
||||||
|
CMS_DB_USER=libreshop
|
||||||
|
CMS_DB_PASSWORD=changeme-in-prod
|
||||||
|
|
||||||
|
# Strapi secrets — generate real values with: openssl rand -base64 32
|
||||||
|
CMS_JWT_SECRET=changeme-jwt-secret-32-bytes-min!!!
|
||||||
|
CMS_API_TOKEN_SALT=changeme-api-token-salt
|
||||||
|
CMS_ADMIN_JWT_SECRET=changeme-admin-jwt-secret
|
||||||
|
CMS_APP_KEYS=key-a,key-b,key-c,key-d
|
||||||
|
CMS_TRANSFER_TOKEN_SALT=changeme-transfer-salt
|
||||||
|
ADMIN_EMAIL_ADDRESS=admin@example.invalid
|
||||||
|
|
||||||
|
# Mail relay — leave blank for stdout-log mode (no real email sent).
|
||||||
|
MAIL_SMTP_RELAY_HOST=
|
||||||
|
MAIL_SMTP_RELAY_PORT=587
|
||||||
|
MAIL_SMTP_RELAY_USERNAME=
|
||||||
|
MAIL_SMTP_RELAY_PASSWORD=
|
||||||
|
|
||||||
|
# Shop runtime — placeholder demo token, no PayPal client.
|
||||||
|
SHOP_API_TOKEN=demo-shop-api-token
|
||||||
|
SHOP_SITE_URL=http://localhost:8080
|
||||||
|
SHOP_BASE_URL=/
|
||||||
|
PAYMENT_ENVIRONMENT=sandbox
|
||||||
|
PAYPAL_CLIENT_ID=
|
||||||
|
|
||||||
|
# Nginx host header (browser sees this; localhost is fine for the demo).
|
||||||
|
NGINX_HOST=localhost
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.swp
|
||||||
|
data/
|
||||||
|
state/
|
||||||
80
README.md
Normal file
80
README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# libreshop/demo
|
||||||
|
|
||||||
|
Standalone preview of the [libreshop](https://git.librete.ch/libreshop)
|
||||||
|
toolkit. Composes `cms`, `mail`, `pdf`, `nginx`, `shop` (plus a
|
||||||
|
Postgres for the CMS) with placeholder env so the stack runs
|
||||||
|
clickably without any adapter.
|
||||||
|
|
||||||
|
This is the white-label preview — no muellerprints branding, no
|
||||||
|
specific product catalogue, no production credentials. Adapters
|
||||||
|
(e.g. [`mp`](https://git.librete.ch/libretech/mp)) replace the
|
||||||
|
`compose.yaml` here with their own composition + branding env.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://git.librete.ch/libreshop/demo
|
||||||
|
cd demo
|
||||||
|
cp .env.example .env
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:8080](http://localhost:8080) — nginx fronts
|
||||||
|
the Nuxt shop on `:9999` and proxies `/api/*` to Strapi on `:5555`.
|
||||||
|
|
||||||
|
Strapi admin: log into the CMS at
|
||||||
|
`http://localhost:8080/admin` and create the first admin user on
|
||||||
|
first launch.
|
||||||
|
|
||||||
|
## What is and isn't in the demo
|
||||||
|
|
||||||
|
In:
|
||||||
|
|
||||||
|
- All five libreshop component images, pinned to `:v0.1.0`.
|
||||||
|
- Postgres for the CMS (`postgres:16-alpine`).
|
||||||
|
- Inter-service networking via the `internal` and `data` docker
|
||||||
|
networks.
|
||||||
|
- Healthchecks per service.
|
||||||
|
|
||||||
|
Out:
|
||||||
|
|
||||||
|
- Real SMTP — mail stays in stdout-log mode unless you fill the
|
||||||
|
`MAIL_SMTP_RELAY_*` block in `.env`.
|
||||||
|
- Real PayPal — `PAYMENT_ENVIRONMENT=sandbox` with no client ID.
|
||||||
|
- TLS — nginx serves plain HTTP on port 8080. Front it with caddy /
|
||||||
|
another reverse proxy + Let's Encrypt for a public deployment.
|
||||||
|
- Production data — the CMS starts empty.
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
`.env.example` pins every component to a `:v0.1.0` tag. To test the
|
||||||
|
rolling `:main` of one component, override its tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
LIBRESHOP_CMS_TAG=main docker compose up -d cms
|
||||||
|
```
|
||||||
|
|
||||||
|
Each component publishes `:main`, `:sha-<7>`, `:vX.Y.Z`, and `:latest`
|
||||||
|
(latest only on tag pushes). For reproducible runs always pin a
|
||||||
|
specific `:vX.Y.Z`.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Strapi takes 2-5 min to start on first run** (DB init + admin
|
||||||
|
setup). The `start_period: 5m` healthcheck accommodates this.
|
||||||
|
- **`pull access denied`** — the libreshop registry is currently
|
||||||
|
read-public, so anonymous pull works. If you see auth errors on
|
||||||
|
pull, either the package was unpublished or your docker daemon
|
||||||
|
is hitting a stale credential. Try `docker logout git.librete.ch`.
|
||||||
|
- **CORS / origin mismatches** — `SHOP_SITE_URL` and `NGINX_HOST` in
|
||||||
|
`.env` must match the URL you actually open in a browser.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
The demo composition follows the libreshop adapter contract — env
|
||||||
|
overrides + bind-mounted volumes only, no `docker exec` patches into
|
||||||
|
running containers. If a new env var is needed for an adapter, raise
|
||||||
|
it on the relevant component repo (e.g.
|
||||||
|
`git.librete.ch/libreshop/cms/issues`) and the demo will pick it up
|
||||||
|
on the next release.
|
||||||
177
compose.yaml
Normal file
177
compose.yaml
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
name: libreshop-demo
|
||||||
|
|
||||||
|
# Standalone preview of the libreshop toolkit. Brings up cms + cms-db
|
||||||
|
# + mail + pdf + nginx + shop with placeholder values so the stack is
|
||||||
|
# clickable without any adapter. Adapters (e.g. mp) replace this file
|
||||||
|
# with their own composition + branding env.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cp .env.example .env # placeholder values, OK for local
|
||||||
|
# docker compose pull
|
||||||
|
# docker compose up -d
|
||||||
|
# open http://localhost:8080
|
||||||
|
#
|
||||||
|
# Component versions are pinned to v0.1.0 so the demo is reproducible
|
||||||
|
# across hosts and survives rolling :main churn.
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
cms-db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${CMS_DB_NAME:-libreshop}
|
||||||
|
POSTGRES_USER: ${CMS_DB_USER:-libreshop}
|
||||||
|
POSTGRES_PASSWORD: ${CMS_DB_PASSWORD:-changeme-in-prod}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "${CMS_DB_USER:-libreshop}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- cms-db-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- data
|
||||||
|
|
||||||
|
cms-permissions:
|
||||||
|
image: alpine
|
||||||
|
user: root
|
||||||
|
volumes:
|
||||||
|
- cms-data:/app/public:rw
|
||||||
|
command: |
|
||||||
|
sh -c "
|
||||||
|
addgroup -g 1000 node 2>/dev/null || true &&
|
||||||
|
adduser -D -u 1000 -G node node 2>/dev/null || true &&
|
||||||
|
mkdir -p /app/public &&
|
||||||
|
chown -R node:node /app/public
|
||||||
|
"
|
||||||
|
|
||||||
|
cms:
|
||||||
|
image: git.librete.ch/libreshop/cms:${LIBRESHOP_CMS_TAG:-v0.1.0}
|
||||||
|
pull_policy: missing
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NODE_ENV: ${NODE_ENV:-production}
|
||||||
|
DATABASE_HOST: cms-db
|
||||||
|
DATABASE_PORT: 5432
|
||||||
|
DATABASE_NAME: ${CMS_DB_NAME:-libreshop}
|
||||||
|
DATABASE_USERNAME: ${CMS_DB_USER:-libreshop}
|
||||||
|
DATABASE_PASSWORD: ${CMS_DB_PASSWORD:-changeme-in-prod}
|
||||||
|
DATABASE_CLIENT: postgres
|
||||||
|
DATABASE_SSL: "false"
|
||||||
|
STRAPI_TELEMETRY_DISABLED: "true"
|
||||||
|
STRAPI_DISABLE_UPDATE_NOTIFICATION: "true"
|
||||||
|
STRAPI_HIDE_STARTUP_MESSAGE: "true"
|
||||||
|
BROWSER: "false"
|
||||||
|
JWT_SECRET: ${CMS_JWT_SECRET:-changeme-jwt-secret-32-bytes-min!!!}
|
||||||
|
API_TOKEN_SALT: ${CMS_API_TOKEN_SALT:-changeme-api-token-salt}
|
||||||
|
ADMIN_JWT_SECRET: ${CMS_ADMIN_JWT_SECRET:-changeme-admin-jwt-secret}
|
||||||
|
APP_KEYS: ${CMS_APP_KEYS:-key-a,key-b,key-c,key-d}
|
||||||
|
TRANSFER_TOKEN_SALT: ${CMS_TRANSFER_TOKEN_SALT:-changeme-transfer-salt}
|
||||||
|
PDF_API_ADDRESS: http://pdf:1111
|
||||||
|
MAIL_API_ADDRESS: http://mail:2222
|
||||||
|
ADMIN_EMAIL_ADDRESS: ${ADMIN_EMAIL_ADDRESS:-admin@example.invalid}
|
||||||
|
volumes:
|
||||||
|
- cms-data:/app/public:rw
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
- data
|
||||||
|
depends_on:
|
||||||
|
cms-permissions:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
cms-db:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: wget --no-verbose --spider -S -T 1 http://cms:5555/_health || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 5m
|
||||||
|
|
||||||
|
mail:
|
||||||
|
image: git.librete.ch/libreshop/mail:${LIBRESHOP_MAIL_TAG:-v0.1.0}
|
||||||
|
pull_policy: missing
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-development}
|
||||||
|
PYTHONUNBUFFERED: "true"
|
||||||
|
# Demo: log mail to stdout instead of relaying. Set the SMTP_RELAY_*
|
||||||
|
# block to send for real.
|
||||||
|
SMTP_RELAY_HOST: ${MAIL_SMTP_RELAY_HOST:-}
|
||||||
|
SMTP_RELAY_PORT: ${MAIL_SMTP_RELAY_PORT:-587}
|
||||||
|
SMTP_RELAY_USERNAME: ${MAIL_SMTP_RELAY_USERNAME:-}
|
||||||
|
SMTP_RELAY_PASSWORD: ${MAIL_SMTP_RELAY_PASSWORD:-}
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: wget --no-verbose --spider -S -T 1 http://mail:2222/health || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
pdf:
|
||||||
|
image: git.librete.ch/libreshop/pdf:${LIBRESHOP_PDF_TAG:-v0.1.0}
|
||||||
|
pull_policy: missing
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-development}
|
||||||
|
PYTHONUNBUFFERED: "true"
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: wget --no-verbose --spider -S -T 1 http://pdf:1111/health || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
shop:
|
||||||
|
image: git.librete.ch/libreshop/shop:${LIBRESHOP_SHOP_TAG:-v0.1.0}
|
||||||
|
pull_policy: missing
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NUXT_SHOP_API_TOKEN: ${SHOP_API_TOKEN:-demo-shop-api-token}
|
||||||
|
NUXT_CMS_INTERNAL_URL: http://cms:5555
|
||||||
|
NUXT_SITE_URL: ${SHOP_SITE_URL:-http://localhost:8080}
|
||||||
|
NUXT_PUBLIC_BASE_URL: ${SHOP_BASE_URL:-/}
|
||||||
|
NUXT_PUBLIC_PAYMENT_ENVIRONMENT: ${PAYMENT_ENVIRONMENT:-sandbox}
|
||||||
|
NUXT_PUBLIC_PAYPAL_CLIENT_ID: ${PAYPAL_CLIENT_ID:-}
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
depends_on:
|
||||||
|
cms:
|
||||||
|
condition: service_started
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "fetch('http://localhost:9999/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: git.librete.ch/libreshop/nginx:${LIBRESHOP_NGINX_TAG:-v0.1.0}
|
||||||
|
pull_policy: missing
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${LIBRESHOP_DEMO_PORT:-8080}:80"
|
||||||
|
environment:
|
||||||
|
NGINX_HOST: ${NGINX_HOST:-localhost}
|
||||||
|
depends_on:
|
||||||
|
shop:
|
||||||
|
condition: service_started
|
||||||
|
cms:
|
||||||
|
condition: service_started
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: service nginx status || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
data:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cms-data:
|
||||||
|
cms-db-data:
|
||||||
Reference in New Issue
Block a user