# Offline license allocation and setup

Staff creates an offline allocation with its customer, product, quantity (1–100),
commercial classification, price and expiry. **Node UUID and deployment UUID are
not required when issuing it.** The dashboard shows `Awaiting activation`.
An allocation is not a bearer credential and does not permit an image download.
Its commercial term starts at the staff-selected effective time, rather than
being extended when a server is activated.

## Activate on the target server

1. Sign into the customer dashboard and open **Licenses → Offline license setup**.
   Download the credential-free setup helper from
   `https://dash.eden.dev/eden/offline-license-setup.py`. Transfer it to the target
   server, which needs Python 3 and OpenSSL with Ed25519 support.
2. Run `prepare` under the **same OS identity as the Eden runtime**. It retains a
   private Ed25519 proof key and stable node/deployment UUIDs, and prints the path
   of `request.json`. Existing `EDEN_CLUSTER_UID` and
   `EDEN_OFFLINE_LICENSE_NODE_UUID` may supply those real identities; subsequent
   runs must match them. Use a separate durable state directory per installation.
3. Bring only `request.json` to the dashboard. Select its allocated license,
   upload the request, review the identities and explicitly approve activation.
   The authority verifies live organization authority and proof of key ownership,
   then atomically binds the allocation, signs the ordinary offline lease and
   grants the matching image entitlement. Download `offline-license.json`.
4. Transfer that response to the original server and run `apply` using its
   unchanged state directory. Start Eden with the generated `eden.offline.env`
   and your independently provisioned authority verification trust files.

For an air-gapped server, transfer these two public-request/private-response
files through your approved transport; the helper makes no network requests.
Never upload `activation-key.pem`, `identity.json`, runtime watermarks or an SSO
session. Protect the returned activation response and lease as credentials.

```sh
python3 offline-license-setup.py prepare --license LICENSE_UUID --state-dir /var/lib/eden/offline
python3 offline-license-setup.py apply --response offline-license.json --state-dir /var/lib/eden/offline
```

`apply` checks that the response identifies this license, node and deployment.
**Eden's normal runtime verifier performs signature, trust, expiry and rollback
checks before it allows service traffic.** The helper does not replace it or
bootstrap a new trust root. Invalid or mismatched responses do not replace an
existing lease. Retain the original key, request, generated identities and
anti-rollback state across restarts; do not regenerate them to recover a license.

## Container ownership and durable paths

The published product images run as UID/GID **10001:10001**. Prepare and apply as
that UID, rather than root or a different host operator. Files are 0600 and the
state directory is 0700. For example, a host administrator can prepare custody:

```sh
sudo install -d -o 10001 -g 10001 -m 0700 /var/lib/eden/offline
sudo setpriv --reuid=10001 --regid=10001 --clear-groups python3 offline-license-setup.py prepare --license LICENSE_UUID --state-dir /var/lib/eden/offline
```

Copy the response into that directory as UID/GID 10001 with mode 0600, then run
`apply` as UID 10001. Use the **same absolute state-directory mount path** inside
the container; generated environment paths are absolute. The mount must be
writable so the runtime can persist its monotonic watermark. Add the following
to the normal product deployment configuration:

```sh
docker run --user 10001:10001 \
  --env-file /var/lib/eden/offline/eden.offline.env \
  --mount type=bind,src=/var/lib/eden/offline,dst=/var/lib/eden/offline \
  --mount type=bind,src=/etc/eden/trust,dst=/etc/eden/trust,readonly \
  -e EDEN_OFFLINE_LICENSE_TRUST_BUNDLE_FILE=/etc/eden/trust/verification-bundle.json \
  -e EDEN_OFFLINE_LICENSE_RECOVERY_KEY_FILE=/etc/eden/trust/recovery-root.pem \
  YOUR_VERIFIED_EDEN_IMAGE
```

Provision the recovery root through an independently trusted channel and supply
its matching signed verification bundle. A single-key deployment can instead
use `EDEN_OFFLINE_LICENSE_TRUST_KEY_FILE`; do not combine that setting with a
trust bundle. Native hosts follow the same custody rules using their actual
runtime user. Preserve all the existing application/network/storage settings
required by your deployment.

## Retry, recovery and revocation

Keep the same request file when a connection is interrupted. Repeating `prepare`
retains its identities and proof key. Repeating activation with that exact proof
returns the original signed credential and expiry; it cannot bind another host.
Activated allocations remain visible for **Recover activated license**, even
when the first response was lost and the page was reloaded. Changed proofs
conflict. Current customer, session, organization, license and entitlement
eligibility are checked under transaction locks for recovery too. Revoked,
suspended or expired licenses cannot recover a credential.

Staff may revoke an unactivated allocation using the normal license revocation
action. Activation never extends the original purchased term. Once delivered,
an offline lease still follows its existing offline revocation/expiry policy;
a server without connectivity cannot observe a later central change instantly.

## HTTP contract

All paths are below `https://license.eden.dev/v1`. These are ordinary customer
session operations; a restricted CLI image-download token is insufficient.

| Method and path | Request | Result |
| --- | --- | --- |
| `GET /tenant/{tenant_uuid}/offline-licenses?offset=0` | Customer session bearer | Up to 100 organization-authorized allocations in immutable creation order; `items` contains license/org IDs, expiry, recorded commercial terms and status; follow `next_offset` until null. Activated rows remain available for recovery. |
| `POST /tenant/{tenant_uuid}/offline-licenses/activate` | Customer session bearer; bounded JSON setup request | `license_uuid`, `node_uuid`, `deployment_uuid`, original `expires_at`, sensitive `license_key`, and `replayed`; `Cache-Control: no-store`. |

Setup JSON has `intent` containing `schema_version: 1`, `license_uuid`,
`operation_uuid`, `node_uuid`, `deployment_uuid`, and canonical standard-base64
32-byte Ed25519 `public_key`, plus a canonical standard-base64 64-byte `signature`.
All UUIDs are non-nil. The signed bytes are the UTF-8 domain
`eden.offline-license.activation.v1` plus one NUL, then the four UUIDs in the
listed order as 16-byte UUIDs, then the raw 32-byte key. The host key is proof
material, not an Eden credential or new trust authority. Request size is limited
to 8192 bytes; the server verifies the signature before allocation lookup.

The activation operation is idempotent by the immutable allocation plus exact
signed request. Missing/invalid session is 401; wrong role, inactive customer,
invalid signature, revoked or expired authority is 403; a missing tenant-local
allocation is 404; a different proof for a consumed allocation is 409. Database
or signer failures return an error without committing partial authority; retry
the original request. Staff issuance remains idempotent by `Idempotency-Key`.
Legacy staff requests that already supply both host UUIDs remain supported.
