Scrydon
DeploymentOperations

License rotation

Apply a renewed license bundle without downtime.

When your license is approaching expiry, your Scrydon account team will send a renewed bundle ({ "jwt": "…", "publicKey": "-----BEGIN PUBLIC KEY-----…" }). This runbook covers applying it.

No pod restart required. The license lives in the platform_config row of the auth database, not in a Kubernetes Secret and not in an init container. Rotation happens in the platform UI and takes effect immediately — api-platform's Better-Auth license plugin reads the row on every request.

Before you start

  • You have the renewed bundle (a JSON file shaped { "jwt": "…", "publicKey": "-----BEGIN PUBLIC KEY-----…" }).
  • An administrator account on the running platform.

Apply the renewed bundle

  1. Sign in to the platform as an admin and open Settings → License.
  2. Click Update license.
  3. Drop the renewed bundle JSON onto the upload area, or paste its contents into the textarea.
  4. Confirm the displayed claims (tier, CPU / RAM / VRAM, expiry). The platform verifies the JWT signature against the public key in the bundle before persisting it.
  5. Click Save.

The new license takes effect immediately on the next request — no pod restart, no Helm change. The previous bundle is overwritten in platform_config.

Tip: want to inspect the bundle before applying it? Use the in-browser License Checker — it decodes the JWT in your browser and shows the same claim summary as the UI step.

Verifying the new license is in effect

After saving, the Settings → License page should refresh with the new claims. From the CLI you can also call the authenticated license endpoint on api-platform:

# Inside the cluster — uses your existing platform session cookie:
curl -fsS https://app.yourdomain.com/api/auth/license/status \
  -H "Cookie: $SCRYDON_SESSION" | jq '{tier, exp, cpu, ram, vram}'

The exp field should match the renewed bundle's expiry.

What happens if a renewal is late

The license enforcement model is hot-loaded from the DB, so the failure mode is different from the old init-container model:

  • The license has a built-in grace period (default 30 days past exp) during which Scrydon continues to serve normally and shows a renewal banner in the UI.
  • After the grace period, license-enforced features (per your contract) start returning a 402-style response with a "Renew your license" message. Read access typically stays available; write paths gate.
  • No pod is ever blocked from starting by a missing or expired license — license enforcement happens in-process at request time, not at boot.

Practical implication: a late renewal degrades the platform progressively after the grace period, but does not lock operators out of kubectl or out of rotating the bundle once it arrives.

Pre-seeding the license at install time

For brand-new installations the same bundle goes into the /setup wizard's first step. You can also pre-seed it via Helm values to skip the License step entirely:

# values.customer.yaml
auth:
  secrets:
    LICENSE: |
      eyJhbGciOiJSUzI1NiIs...        # bundle.jwt
    LICENSE_PUBLIC_KEY: |
      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFA...   # bundle.publicKey
      -----END PUBLIC KEY-----

These values are read once on first install to seed the platform_config row; after that, the DB is the source of truth and the values entries can be removed at the next helm upgrade without affecting the platform.

Decoding a license

If you need to confirm what's in a bundle before applying it, use the in-browser License Checker. It runs locally — no network calls — and shows the tier, CPU / RAM / VRAM, customer identifier, and exp timestamp.

To decode the JWT manually:

JWT=$(jq -r .jwt license.bundle.json)
SEG=$(echo "$JWT" | cut -d. -f2 | tr '-_' '+/')
PAD=$(( (4 - ${#SEG} % 4) % 4 ))
PAYLOAD=$(printf '%s' "$SEG$(printf '=%.0s' $(seq 1 $PAD 2>/dev/null))" | openssl base64 -d -A)
echo "$PAYLOAD" | jq .

Confirm:

  • exp — the expiry timestamp.
  • sub — your customer identifier.
  • scrydon.entitlements — your contracted resource limits.
On this page

On this page