Files
mivanchenko c3e520aabb
Test backoffice (smb-crm) / test (push) Successful in 1m43s
Rate-limit public bookings per contact; add direct owner contact endpoint
Public booking API now rejects a 6th active booking from the same
customer_contact within 24h (429), stopping one contact from filling
every slot on every resource, while owner-entered manual bookings stay
unaffected.

Add POST /api/contact: client sites can reach their own owner's inbox
directly (via their existing login email) for general inquiries,
separate from the agency's leads/Telegram pipeline (n8n/lead-intake.json),
which stays reserved for actual prospects contacting the agency itself.
Paris Barber Shop's contact form and Rückruf widget now point here; the
Rückruf floating widget itself has been removed from the site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 04:47:06 +02:00
..

clients/ — one isolated stack per business client

Each signed client gets their own Compose project (smb-client-<slug>) running a single lightweight nginx:alpine container that serves their static site on their domain. Clients are fully separated: independent lifecycle, logs, and removal; no shared container or state. Booking and lead data still flow into the shared CRM (Postgres source of truth) via the n8n webhooks baked into each client's page.

Why a container per client (not one shared host)

  • Isolation — update / restart / remove one client without touching any other.
  • Clean billing & offboardingdocker compose down removes exactly that client.
  • Cheap — static nginx idles at a few MB; capped at 64 MB each, so dozens fit the 16 GB box.

Layout

deploy/clients/
├── _template/            # DON'T deploy — the source template
│   ├── docker-compose.yml   (name: smb-client-${CLIENT_SLUG})
│   ├── .env.example
│   └── site/index.html
├── new-client.sh         # scaffolds a client folder from _template/
└── <slug>/               # one folder per real client (created by the script)
    ├── docker-compose.yml
    ├── .env              (CLIENT_SLUG, CLIENT_DOMAIN)
    └── site/             (the client's branded page)

Add a client

cd deploy/clients
./new-client.sh happynails happynails.de ../../templates/landing/preview-happynails

Then follow the printed steps: add the Caddy block + reload, point DNS, and docker compose up -d on the homelab. Each client lives at /home/mivanchenko/clients/<slug>/ on the box.

How this fits the whole system (compose groups)

edge/        caddy                    → TLS, routing, basic-auth   (one, shared)
automation/  n8n (+ postgres, redis)  → n8n.mivanchenko.de         (one, shared)
crm/         smb-db + smb-crm         → onboard.mivanchenko.de/crm (one, shared)
demos/       smb-demos                → demos.mivanchenko.de       (prospect previews)
clients/     smb-client-<slug> × N    → each client's domain       (one per client) ← this dir

Shared infra (edge / automation / crm) stays single; only the client-facing sites multiply, one isolated stack each. Previews for prospects stay in demos/ until they sign — then scaffold them a real isolated stack here.