Distributor key console (white-label branding removed)
White-label branding was removed in 1.72.2. The product name, logo, subtitle, accent colour, company fields, the About HTML block, signed branding bundles and the
branding.*RPCs no longer exist, andbranding.json/branding.bundle.jsonfiles are ignored. Every dashboard shows the DuDuClaw identity and the fixed upstream vendor credit (嘟嘟數位科技有限公司 / Dudu Technology Ltd.), which is compiled into the binary and cannot be changed through config or any RPC.A distributor build can carry one block of distributor information on the About page. The distributor maintains it in the license console; it is not a dashboard setting: see distributor-notice.md.
This page now describes only the vendor-side distributor key console.
For the vendor (distributor console)
Section titled “For the vendor (distributor console)”The admin-only /manage/distributors page books resellers and issues machine-bound OEM license keys.
-
Configure the issuer signing key (the same 32-byte Ed25519 seed format produced by the license keygen tooling, base64, single line):
~/.duduclaw/config.toml [distributor]issuer_key_path = "/path/to/license-signing-v2.key"Unset = issuance is refused with an explicit error; the console shows a setup card instead. The key content never appears in logs or responses.
-
Add a distributor, then Issue key: paste the machine fingerprint the reseller gets from
duduclaw license fingerprint, pick a term (default 365 days), and copy the generated blob. The reseller activates it withduduclaw license activate <blob>.Every issued license is self-verified against the binary’s baked v2 public key before it is recorded — a mismatched key pair fails loudly.
Take the fingerprint from 1.66.1 or later.
- macOS: since 1.66.1 the fingerprint binds to
IOPlatformUUID. macOS 26 (Darwin 27) hides every interface’s MAC from ordinary non-entitled processes (all of them read as02:00:00:00:00:00), so selecting a better interface cannot help and the identifier itself changed. Every existing macOS install’s fingerprint therefore changes. - Linux / Windows: the fingerprint is unchanged. Selection only skips all-zero, placeholder, and multicast addresses; a host whose first interface was already usable computes exactly the 1.66.0 value.
Moving machines, or after an upgrade: run
duduclaw license fingerprinton the new (or upgraded) machine and have the key re-issued against that value, or runduduclaw license rebind(which calls the control plane’s/v1/license/rebindto re-sign). A license issued against an older fingerprint keeps working — the gateway accepts the legacy binding and logs a WARN recommending a re-issue.duduclaw license statusshows aFingerprint binding:line reading eitherstrongorlegacy (re-issue recommended). To see every fingerprint this machine accepts, runduduclaw license fingerprint --all; line 1 is always the strong fingerprint, so scripts can keep reading the first line.If
fingerprint/statuswarn that no hardware identity is readable, the machine has neither a platform UUID nor a usable NIC address (a container or restricted environment) and the fingerprint binds to the hostname alone. It is still stable and fine to issue against — it just provides no hardware-level copy protection.Moving machines, or after an OS upgrade: run
duduclaw license fingerprinton the new (or upgraded) machine and have the key re-issued against that value. A license issued against an older fingerprint keeps working on 1.66.1 — the gateway accepts the legacy binding and logs a WARN recommending a re-issue.duduclaw license statusshows aFingerprint binding:line reading eitherstrongorlegacy (re-issue recommended). To see every fingerprint this machine accepts, runduduclaw license fingerprint --all; line 1 is always the strong fingerprint, so scripts can keep reading the first line. - macOS: since 1.66.1 the fingerprint binds to
-
Revoke marks the key revoked in the local ledger (
distributor.db) and is written to the security audit log. Propagation to an already-activated instance happens through the phone-home refresh and the signed CRL described below — the UI states the timing honestly rather than pretending instant revocation.
Keeping issued keys alive (refresh & revocation)
Section titled “Keeping issued keys alive (refresh & revocation)”When an issuer key is configured, the owner gateway also serves a lightweight
control-plane for the keys it signs, so they never trip the 60-day offline
downgrade and revocations propagate. Two public endpoints self-gate on
[distributor] issuer_key_path (unset ⇒ 404, so a plain gateway exposes
nothing):
| Endpoint | Purpose |
|---|---|
POST /v1/license/refresh |
Re-signs the caller’s license with last_phone_home = now. Never extends the term — a lapsed key is renewed by re-issuing, not refreshing. Returns revoked for a revoked key, 403 for a fingerprint mismatch or an expired key. |
GET /v1/license/crl |
A signed Certificate Revocation List (Ed25519 over the same canonical payload the client verifies) listing every revoked subscription_id. TTL 7 days. |
The distributor’s instance needs no code change — point it at the owner gateway with one environment variable:
# On the reseller's DuDuClaw instanceexport DUDUCLAW_CONTROL_URL=https://your-gateway.example.comWith that set, the reseller’s gateway phones home on its per-tier schedule and polls the CRL, so:
- Refresh keeps the license alive indefinitely (each successful phone-home
re-stamps
last_phone_home), and the owner console shows the last refresh time per key as a still-alive signal. - Revocation reaches the reseller within the phone-home interval (roughly a week for the OEM tier) and, independently, within the CRL polling window (24h) — whichever lands first.
The distributor console shows an Endpoint active badge and this setup snippet whenever an issuer key is configured.
Honest condition: if a reseller does not set
DUDUCLAW_CONTROL_URL(and no cloud control-plane is reachable), the key still downgrades to the open-source tier after 60 days without a successful phone-home. The refresh endpoint removes the downgrade only for instances that point at it.
Baking the endpoint into the key ([distributor] public_url)
Section titled “Baking the endpoint into the key ([distributor] public_url)”To spare the reseller from setting DUDUCLAW_CONTROL_URL at all, declare the
owner gateway’s externally-reachable URL once:
# ~/.duduclaw/config.toml (owner instance)[distributor]issuer_key_path = "/path/to/license-signing-v2.key"public_url = "https://your-gateway.example.com"From then on, distributor.issue embeds that URL into the key as a self-carried
control_url. The distributor instance resolves its control-plane in this
order: DUDUCLAW_CONTROL_URL env → the key’s control_url → the built-in
default. So a key issued with public_url set phones home and refreshes with
zero client configuration, and duduclaw license refresh works without any
environment variable. control_url is not part of the signed payload (editing
it needs local write access to the 0600 license.json, and every refresh
response is itself signature-verified — the worst case is “URL unreachable”, the
same as today). A refresh preserves the original control_url.
RPC surface
Section titled “RPC surface”| Method | Access |
|---|---|
about.get |
any logged-in user (fixed product and vendor fields, version, tier; distributor_notice only from a distributor build or a signed runtime copy for a distributor-issued license) |
distributor.status/list/add/update/remove/issue/revoke/upgrade |
admin |
HTTP control-plane surface (owner gateway, issuer-key-gated)
Section titled “HTTP control-plane surface (owner gateway, issuer-key-gated)”| Endpoint | Purpose |
|---|---|
POST /v1/license/refresh |
Re-sign a license (phone-home). |
GET /v1/license/crl |
Signed revocation list. |