commit f3b663d36fed6d20c42234c77a88ed0f141b40a7 Author: Michael Czechowski Date: Wed Apr 29 18:18:44 2026 +0200 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__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. diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..326d324 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb8da1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.env.local +*.swp +data/ +state/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dde62d --- /dev/null +++ b/README.md @@ -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. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..3238374 --- /dev/null +++ b/compose.yaml @@ -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: