docs(license): PR 2 deploy + operator instructions
ADMIN.md gains a "Running a Gumroad webhook" section: how the URL secret works, how to add a SKU to products.yaml, how to inspect gumroad_events (recent activity + failures-only queries), how to replay a failed delivery, and how to test without buyers via Gumroad's "Send Test Ping" button. The deployed-vs-queued matrix flips Gumroad + Postmark to "code merged, deploy pending" so it's clear the bits exist on main but the live box still runs PR 1. SETUP-LICENSE-SERVER.md §3 commits the eventual compose.yml shape with PR 2 environment + secrets lines included but commented out, ready to uncomment at deploy time. The §3 chown step already covers the new secret files because it uses `chmod 400 secrets/*` / `chown 10001:10001 secrets/*`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -167,13 +167,55 @@ verifies against, so existing buyers continue to work.
|
|||||||
| Mint API + Postgres + auth | **Live** |
|
| Mint API + Postgres + auth | **Live** |
|
||||||
| `datatools-admin` CLI (manual mints) | **Live** |
|
| `datatools-admin` CLI (manual mints) | **Live** |
|
||||||
| `licenses.datatools.unalogix.com/health` public | **Live** |
|
| `licenses.datatools.unalogix.com/health` public | **Live** |
|
||||||
| Gumroad webhook receiver | **PR 2** |
|
| Gumroad webhook receiver | **PR 2 — code merged, deploy pending** |
|
||||||
| Postmark transactional email | **PR 2** |
|
| Postmark transactional email | **PR 2 — code merged, deploy pending** |
|
||||||
| Buyer renewal / re-delivery portal | **PR 3** |
|
| Buyer renewal / re-delivery portal | **PR 3** |
|
||||||
| Cloudflare in front (DDoS / WAF) | Deferred (DNS at supercp/cPanel) |
|
| Cloudflare in front (DDoS / WAF) | Deferred (DNS at supercp/cPanel) |
|
||||||
| Production signing keypair | Deferred (still using dev key) |
|
| Production signing keypair | Deferred (still using dev key) |
|
||||||
| Automated DB backups | **Pending** — see §"Backups" |
|
| Automated DB backups | **Pending** — see §"Backups" |
|
||||||
|
|
||||||
|
### Running a Gumroad webhook (PR 2)
|
||||||
|
|
||||||
|
Once PR 2 is deployed, sales fire `POST` to
|
||||||
|
`https://licenses.datatools.unalogix.com/webhooks/gumroad?secret=<gumroad_secret>`.
|
||||||
|
Auth is the URL secret (Gumroad's recommended pattern). The handler
|
||||||
|
audit-logs the raw payload, mints idempotently keyed on `sale_id`,
|
||||||
|
sends the buyer their blob via Postmark, and returns 200 (always —
|
||||||
|
non-2xx would trigger 3-day retry storms).
|
||||||
|
|
||||||
|
**Adding a new SKU:**
|
||||||
|
|
||||||
|
1. Create the product in Gumroad and copy its `product_id`.
|
||||||
|
2. Edit `/srv/datatools-license/app/server/config/products.yaml`,
|
||||||
|
add a row under `gumroad:` with that ID + the tier you sold.
|
||||||
|
3. `cd /srv/datatools-license && docker compose restart api` — the
|
||||||
|
config is read at startup and cached.
|
||||||
|
|
||||||
|
**Inspecting webhook activity:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Recent webhook deliveries (all storefronts share this table)
|
||||||
|
ssh michael@46.225.166.142 'cd /srv/datatools-license && docker compose exec -T postgres \
|
||||||
|
psql -U datatools_api -d datatools_licenses -c \
|
||||||
|
"SELECT received_at, order_id, processed, error FROM gumroad_events ORDER BY received_at DESC LIMIT 20;"'
|
||||||
|
|
||||||
|
# Failures only (replay candidates)
|
||||||
|
ssh michael@46.225.166.142 'cd /srv/datatools-license && docker compose exec -T postgres \
|
||||||
|
psql -U datatools_api -d datatools_licenses -c \
|
||||||
|
"SELECT id, received_at, order_id, error FROM gumroad_events WHERE processed=false ORDER BY received_at DESC;"'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replaying a failed webhook** (after fixing the products.yaml mapping
|
||||||
|
or whatever surfaced the error): the safest path is to ask the buyer
|
||||||
|
to re-trigger via Gumroad's "Send Test Ping" button in their order
|
||||||
|
record, *or* mint manually via `datatools-admin mint --source manual`
|
||||||
|
and add a note linking to the original `gumroad_events.id`.
|
||||||
|
|
||||||
|
**Testing without buyers:** Gumroad's seller dashboard has a "Send
|
||||||
|
Test Ping" button. It sets `test=true` in the payload; the adapter
|
||||||
|
tags the resulting license with `notes='gumroad test ping'` so it's
|
||||||
|
trivially filterable later.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## TL;DR — I just need a license for my dev machine
|
## TL;DR — I just need a license for my dev machine
|
||||||
|
|||||||
@@ -204,13 +204,18 @@ services:
|
|||||||
DATABASE_URL: postgresql+psycopg://datatools_api@postgres:5432/datatools_licenses
|
DATABASE_URL: postgresql+psycopg://datatools_api@postgres:5432/datatools_licenses
|
||||||
PG_PASSWORD_FILE: /run/secrets/pg_password
|
PG_PASSWORD_FILE: /run/secrets/pg_password
|
||||||
DATATOOLS_ADMIN_TOKEN_FILE: /run/secrets/admin_token
|
DATATOOLS_ADMIN_TOKEN_FILE: /run/secrets/admin_token
|
||||||
# PR 2 adds: POSTMARK_TOKEN_FILE, GUMROAD_WEBHOOK_SECRET_FILE.
|
# PR 2 — uncomment when Postmark + Gumroad are provisioned.
|
||||||
|
# POSTMARK_TOKEN_FILE: /run/secrets/postmark_token
|
||||||
|
# GUMROAD_WEBHOOK_SECRET_FILE: /run/secrets/gumroad_secret
|
||||||
# Production keypair (replaces in-tree dev key): set
|
# Production keypair (replaces in-tree dev key): set
|
||||||
# DATATOOLS_LICENSE_PRIVKEY_FILE: /run/secrets/license_privkey
|
# DATATOOLS_LICENSE_PRIVKEY_FILE: /run/secrets/license_privkey
|
||||||
# and DATATOOLS_LICENSE_PUBKEY: <hex> before shipping v1.0.
|
# and DATATOOLS_LICENSE_PUBKEY: <hex> before shipping v1.0.
|
||||||
secrets:
|
secrets:
|
||||||
- pg_password
|
- pg_password
|
||||||
- admin_token
|
- admin_token
|
||||||
|
# PR 2:
|
||||||
|
# - postmark_token
|
||||||
|
# - gumroad_secret
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8090:8000" # localhost-only; nginx is the only path in
|
- "127.0.0.1:8090:8000" # localhost-only; nginx is the only path in
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -222,8 +227,11 @@ services:
|
|||||||
secrets:
|
secrets:
|
||||||
pg_password: { file: ./secrets/pg_password }
|
pg_password: { file: ./secrets/pg_password }
|
||||||
admin_token: { file: ./secrets/admin_token }
|
admin_token: { file: ./secrets/admin_token }
|
||||||
# PR 2 adds: postmark_token, gumroad_secret. Production keypair
|
# PR 2:
|
||||||
# rotation adds: license_privkey.
|
# postmark_token: { file: ./secrets/postmark_token }
|
||||||
|
# gumroad_secret: { file: ./secrets/gumroad_secret }
|
||||||
|
# Production keypair rotation adds:
|
||||||
|
# license_privkey: { file: ./secrets/license_privkey }
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
datatools_pg_data:
|
datatools_pg_data:
|
||||||
@@ -244,10 +252,12 @@ openssl rand -hex 32 > secrets/pg_password
|
|||||||
# on your laptop to talk to /internal/* via the SSH tunnel.
|
# on your laptop to talk to /internal/* via the SSH tunnel.
|
||||||
openssl rand -hex 32 > secrets/admin_token
|
openssl rand -hex 32 > secrets/admin_token
|
||||||
|
|
||||||
# --- PR 2 / production-key follow-ups (skip for PR 1 bring-up) ---
|
# --- PR 2 secrets ---
|
||||||
# echo -n "<postmark-server-token>" > secrets/postmark_token
|
# echo -n "<postmark-server-token>" > secrets/postmark_token # from postmarkapp.com
|
||||||
|
# openssl rand -hex 32 > secrets/gumroad_secret # paste into Gumroad's Ping URL: ?secret=<this>
|
||||||
|
#
|
||||||
|
# --- production-key follow-up (defer until v1.0 cutover) ---
|
||||||
# echo -n "<ed25519-private-hex>" > secrets/license_privkey
|
# echo -n "<ed25519-private-hex>" > secrets/license_privkey
|
||||||
# openssl rand -hex 32 > secrets/gumroad_secret
|
|
||||||
|
|
||||||
# Lock everything down. The numeric 10001 matches the in-container
|
# Lock everything down. The numeric 10001 matches the in-container
|
||||||
# `app` user (Dockerfile-defined), letting the API read the file
|
# `app` user (Dockerfile-defined), letting the API read the file
|
||||||
|
|||||||
Reference in New Issue
Block a user