Files
juxtapoandClaude Opus 5 a58a61b98c Pak Jabit: full stack — storefront, Spree customisations, catalogue tooling
A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.

What is here:
  storefront/  the Next.js app
  backend/     ONLY the custom Spree code. Spree itself is a prebuilt image
               that mounts no app code, so every file here needs an explicit
               volume entry in docker-compose.yml or it is silently ignored.
  catalogue/   the scripts that loaded and maintain the products
  docs/        manifest, mock registry, napkins

Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.

The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 22:09:55 +08:00

4.3 KiB

🧨 LANDMINE — where every key lives and what it opens

This repo is on the house's private Gitea (gitmirah:3000, Tailscale-only), so real .env files are committed on purpose. That is a deliberate trade: no .env.example drift, no "works on my box", and anyone cloning has a running stack. It also means a clone of this repo is a full set of production credentials. Treat it that way.

Before this repo goes anywhere else — a public mirror, GitHub, a zip to a client — every secret below has to be rotated first. Deleting the file later does not help; git keeps history.


What each key opens

Key Lives in Opens Blast radius if leaked
SPREE_ADMIN_KEY catalogue/.env Full Spree admin API 🔴 Total. Create/delete/reprice every product, read every order and customer.
SECRET_KEY_BASE backend/.env Rails signing 🔴 Forges sessions and signed URLs. Rotating breaks every signed image URL and the admin API key — see below.
NEXT_PUBLIC_SPREE_API_KEY storefront/.env.production Spree store API 🟡 Low. Public by design — it ships to every browser. Read products, create carts. Not admin.
EASYPARCEL_CLIENT_ID / _SECRET storefront/.env.production EasyParcel account 🟠 Book real shipments, spend real money.
PHOTOROOM_API_KEY backend/.env Photoroom sandbox 🟢 Low. Watermarked output, 1000/month free. The live key is not in this repo.

Not in this repo

Thing Where it actually is
Photoroom live key /root/.photoroom/credentials on the house box (chmod 600). 10 images/month.
Spree admin password Nowhere written down. One shared account — see below.
GCloud SSH key /root/.ssh/google_compute_engine on the house box.
storefront/.easyparcel-token.json A cached OAuth token, committed with the rest. It expires and regenerates itself; harmless, but it is a live credential while valid.

🔴 Do not rotate SECRET_KEY_BASE casually

It signs more than sessions. Changing it:

  • breaks every ActiveStorage image URL — 3,228 blobs, the whole catalogue goes blank
  • invalidates the admin API key, so every script in catalogue/ stops working

To force everyone out of the admin, change the admin password instead — that invalidates sessions without touching signed URLs.

🔴 One shared admin account

Every admin session is indistinguishable. This already cost an investigation: an unknown IP (42.188.122.106, Windows/Brave, valid session cookie) was browsing /admin/api_keys and /admin/admin_users, and there was no way to attribute it. It turned out to be a colleague.

~5 people are expected to use the admin. Give each one their own account (Settings → Users) before that number grows.


Where the boxes are

Host Access
Storefront celest-proto 34.21.217.116 ssh ijat@34.21.217.116
Backend + DB pak-jabit-backend 35.247.170.3 ssh -i ~/.ssh/google_compute_engine ijat@35.247.170.3

Public: shop.jux-lab.com · api.jux-lab.com · admin.jux-lab.com


🔴 The custom backend code exists ONLY as bind mounts

ghcr.io/spree/spree is prebuilt and mounts no app code. Everything under backend/config/ and backend/app/ needs an explicit volume entry in docker-compose.yml, or the container simply never loads it — no error, no warning, dead file. The 10MB body-limit initializer sat on the host for a whole day doing nothing before anyone noticed.

None of it is in the database. A rebuilt box without these files comes back with no Image Studio, no address labels, no region preference, and no EasyParcel rates.


Sandbox things that are NOT real

Full list in docs/MOCKS.md. The ones that matter most:

  • Test Card (sandbox) — a Bogus gateway that approves any card ending in the right digits. Deactivate before launch.
  • Test Payment — offline placeholder, no money moves.
  • Standard Delivery (South Africa) R99 and Free Shipping US $0 — placeholder rates, not quotes. (Malaysia is real: live EasyParcel.)
  • studio-test-chinos — a fake product, kept deliberately as the worked example for the Image Studio.

Written 2026-09-15, the night the stack got its region anchor and a customer-owned cart.