# FormaPrint OEM — Distribution Kit

Run **FormaPrint OEM** on your own infrastructure, licensed against
Celestryll's Licentia authority. This kit is everything you need to bring
the stack up with Docker: a `docker-compose.yml` that pulls Celestryll's
published images, an `.env` you fill in, and the steps below.

> **Prefer to let an AI agent do it?** If you use Claude Code (or a
> similar coding agent), point it at **[`CLAUDE-SETUP.md`](CLAUDE-SETUP.md)** —
> a runbook that automates most of this setup for you.

## What FormaPrint OEM is

FormaPrint is a multi-tenant document/report generation service. You send
it structured data plus a template (Work Orders, Inventory Slips, Shipping
Docs, Invoices, and more ship as starter templates) and it renders and
archives a PDF, with a tamper-evident audit trail. It exposes a REST API
plus a file-drop ingestion worker, and validates its license against
Celestryll on each run.

## Prerequisites

- **Docker** and **Docker Compose** (Docker Desktop, or Docker Engine +
  the `docker compose` plugin).
- A **Celestryll account and a FormaPrint license** — sign up at
  **https://celestryll.com/get-started**. A trial gives you a **Demo**
  license (30-day, online validation); purchasing upgrades you to a
  **Production** license. Either way you receive a **TenantId**,
  **LicenseKey**, and **ApiKey**.
- A **SQL Server or Azure SQL database** you control (the connection
  string is yours). No database of your own? See the optional bundled
  `sql` service in `docker-compose.yml` (fine for evaluation; use a
  managed, backed-up database for production).
- A SQL client to apply the schema once — `sqlcmd`, Azure Data Studio, or
  SSMS.

## Setup

### 1. Sign up and get your license

Create your account at **https://celestryll.com/get-started** and obtain
your **TenantId**, **LicenseKey**, and **ApiKey**. Keep them handy for the
next step.

### 2. Configure `.env`

Copy the template and fill it in:

```bash
cp .env.template .env
```

Edit `.env`:

- **From Celestryll:** `FORMAPRINT__LICENTIA__TENANTID`,
  `FORMAPRINT__LICENTIA__LICENSEKEY`, `FORMAPRINT__LICENTIA__APIKEY`.
- **Your infrastructure:** `FORMAPRINT_CONNECTION` (your SQL connection
  string), `FORMAPRINT__SCHEMANAME` (leave `forma` unless you share one
  database across products).
- **Prefilled (licensed):** leave `FORMAPRINT__LICENTIA__ENDPOINT` as
  `https://api.celestryll.com/licensing`.
- **Demo / evaluation (no license):** set `FORMAPRINT__LICENTIA__ENDPOINT=`
  (blank) and leave the section (a) credentials empty. A blank endpoint runs
  the permissive demo stand-in (the license gate is not registered). The
  compose default for this variable is empty, so blanking it in `.env` truly
  engages demo mode. Evaluation only — do not run production unlicensed.

`.env` is git-ignored. Never commit it or paste it into shared output.

### 3. Generate the internal secrets

Two secrets are yours to generate — they are **not** provided by
Celestryll. Use strong, unique random values (32+ bytes):

```bash
# JWT signing key (signs/validates FormaPrint's bearer tokens)
openssl rand -base64 48

# Audit HMAC key (keys the tamper-evident audit chain)
openssl rand -base64 48
```

Put the two values in `.env` as `FORMAPRINT__JWT__SIGNINGKEY` and
`FORMAPRINT__AUDIT__HMACKEY`. Store a copy in your secret manager — losing
the audit key means older audit rows can no longer be re-verified.

### 4. Apply the database schema

FormaPrint's tables, stored procedures, and starter templates are created
by an ordered set of SQL scripts, **shipped in this kit's [`./sql/`](sql/)
directory**. This kit includes an **apply-schema helper** that runs them in
the correct order with the required settings — use it rather than hand-running
each of the 14 files:

```bash
# bash (Linux/macOS/WSL/Git Bash) — from the kit directory
./apply-schema.sh --database FormaPrint --schema forma \
  --server localhost,1433 --user sa --password '<sa-password>'
```

```powershell
# PowerShell (Windows)
./apply-schema.ps1 -Database FormaPrint -Schema forma `
  -Server localhost,1433 -User sa -Password '<sa-password>'
```

The helper connects as an **admin/migrator** (`sa` or a DB owner), creates the
database if absent, and applies every script in `./sql/` in numeric order,
passing your `SchemaName` (default `forma`). It always sets
`QUOTED_IDENTIFIER ON` (sqlcmd's `-I`) and aborts on error (`-b`). You need a
`sqlcmd` on your PATH (from mssql-tools18) or pass `--sqlcmd <path>`.

> **Why the helper, not raw `sqlcmd -i`?** Several scripts create filtered
> indexes, which require `SET QUOTED_IDENTIFIER ON`. Plain `sqlcmd` defaults it
> **OFF**, so hand-running a script without `-I` fails. The helper sets it.

Decision points:

- **`sa` is only for this one-time schema apply.** Your app's **runtime**
  connection string (`FORMAPRINT_CONNECTION`) must use the least-privilege
  `formaprint_app` login that `12_AppSecurity.sql` creates — Production refuses
  an `sa` runtime connection at boot.
- **On Azure SQL, skip `12_AppSecurity.sql`.** It runs `CREATE LOGIN` (a
  server-level statement Azure SQL doesn't support). Apply the rest, then
  create a contained user for the app and grant it
  `SELECT/INSERT/UPDATE/DELETE/EXECUTE` on `SCHEMA::forma` only. On self-hosted
  SQL Server, `12_AppSecurity.sql` provisions the least-privilege
  `formaprint_app` login (dev password `FormaPrint!App#Dev1`; set your own with
  `ALTER LOGIN`) — use it in `FORMAPRINT_CONNECTION`.
- The scripts are **idempotent** — safe to re-run when you upgrade.

### 5. Start the stack

```bash
docker compose up -d
```

This pulls Celestryll's published API and worker images and starts them
against your database and license.

### 6. Verify health

```bash
curl -fsS http://localhost:5000/health/live      # {"status":"live"}
curl -fsS http://localhost:5000/health/ready      # reports DB readiness
```

`/health/ready` returning healthy confirms the API reached your database.
(Adjust the port if you changed `FORMAPRINT_API_PORT`.)

### 7. Render a test document

Every API call is authenticated with a FormaPrint bearer token signed by
**your** `FORMAPRINT__JWT__SIGNINGKEY`. Mint a short-lived service token
with that key (HS256, `iss=FormaPrint`, `aud=FormaPrint`, `scope=service`)
using any JWT tool, then generate a report against the seeded demo tenant
and the seeded Work Order template:

```bash
TOKEN=<service token signed with your JWT signing key>
TENANT=11111111-1111-1111-1111-111111111111   # seeded DemoTenant
FACTORY=22222222-2222-2222-2222-222222222222   # seeded MainPlant

curl -fsS -X POST http://localhost:5000/reports/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: $TENANT" -H "X-Factory-Id: $FACTORY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "33333333-3333-3333-3333-333333333333",
    "documentType": "WorkOrder",
    "outputFormat": "pdf",
    "data": { "workOrderNumber": "WO-1001", "status": "Released",
      "priority": "High", "assignedTo": "Line Crew B",
      "factoryName": "MainPlant", "customer": { "name": "Acme" },
      "operations": [ { "seq": 10, "description": "Mill housing",
        "machine": "MILL-04", "standardHours": 1.5 } ],
      "materials": [ { "sku": "HSG-1001", "description": "Housing blank",
        "quantity": 1, "uom": "EA" } ] } }'
# -> { "success": true, "reportId": "...", "download": "/reports/.../download", ... }
```

Then download the archived PDF (should start with `%PDF-`):

```bash
curl -fsS http://localhost:5000/reports/<reportId>/download \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: $TENANT" -H "X-Factory-Id: $FACTORY" -o test.pdf
```

## Configuration reference

| `.env` variable | Who supplies it | Purpose |
|---|---|---|
| `FORMAPRINT__LICENTIA__TENANTID` | Celestryll (sign-up) | Your Licentia tenant id |
| `FORMAPRINT__LICENTIA__LICENSEKEY` | Celestryll (sign-up) | Your license key |
| `FORMAPRINT__LICENTIA__APIKEY` | Celestryll (sign-up) | API key sent as `X-Licentia-ApiKey` |
| `FORMAPRINT_CONNECTION` | You | Your SQL connection string |
| `FORMAPRINT__SCHEMANAME` | You | SQL schema (default `forma`) |
| `FORMAPRINT__JWT__SIGNINGKEY` | You (generate) | Signs/validates API bearer tokens |
| `FORMAPRINT__AUDIT__HMACKEY` | You (generate) | Keys the tamper-evident audit chain |
| `FORMAPRINT__LICENTIA__ENDPOINT` | Prefilled | `https://api.celestryll.com/licensing` |
| `FORMAPRINT__LICENTIA__FAILOPEN` | Prefilled | Deny (`false`) on Licentia outage |
| `FORMAPRINT_VERSION` | Optional | Published image tag (default `latest`) |
| `FORMAPRINT_API_PORT` | Optional | Host port for the API (default `5000`) |

## Upgrading

Pin a version in `.env` and bump it when Celestryll publishes a release:

```bash
# in .env
FORMAPRINT_VERSION=1.4.0

docker compose pull
docker compose up -d
```

Re-apply the `sql/` scripts for the new version (they are idempotent) if
the release notes call for schema changes.

## Troubleshooting

- **`401` / `LICENSE_REJECTED` (or `402`) on report generation** — the
  license didn't validate. Check `FORMAPRINT__LICENTIA__LICENSEKEY`,
  `FORMAPRINT__LICENTIA__APIKEY`, and `FORMAPRINT__LICENTIA__TENANTID`
  match what Celestryll issued, and that
  `FORMAPRINT__LICENTIA__ENDPOINT` is
  `https://api.celestryll.com/licensing`. A license binds to the first
  machine that validates it; if you moved hosts, contact support for a
  transfer.
- **`401 Unauthorized` with no license reason** — your request's bearer
  token isn't valid for **your** `FORMAPRINT__JWT__SIGNINGKEY`. Re-mint
  the token with the current key (see step 7).
- **API won't start / `/health/ready` unhealthy** — database connectivity.
  Verify `FORMAPRINT_CONNECTION` (host, port, credentials, `Encrypt` /
  `TrustServerCertificate`), that the database exists, and that the schema
  scripts were applied. Check logs: `docker compose logs formaprint-api`.
- **App can't log in to SQL** — confirm you used the least-privilege app
  user (not `sa`) in `FORMAPRINT_CONNECTION` and granted it rights on
  `SCHEMA::forma`.

## Support

- Get started / manage your license: **https://celestryll.com/get-started**
- Support: **support@celestryll.com**

When contacting support, include your **TenantId** and the API logs
(`docker compose logs formaprint-api`). **Never** send your LicenseKey,
ApiKey, JWT signing key, or audit HMAC key.
