#!/usr/bin/env python3 """Mint a signed license blob for a buyer. Creator-only tool. Reads the active HMAC secret from the environment (``$DATATOOLS_LICENSE_SECRET``) — point it at the same secret baked into the shipped binary or the result will fail to verify. Examples -------- Mint a 1-year CORE license for Jane Doe:: python scripts/generate_license.py \\ --name "Jane Doe" --email jane@example.com --tier core Mint a 2-year PRO license and write the blob to a file:: python scripts/generate_license.py \\ --name "Acme Corp" --email ops@acme.com --tier pro \\ --years 2 --output acme.dtlic Re-sign with a custom secret (useful for staged rollouts):: DATATOOLS_LICENSE_SECRET=shipping-secret-2026 \\ python scripts/generate_license.py --name ... --email ... The output is a single base64-encoded token starting with ``DTLIC1:`` — paste this whole string into the buyer's delivery email or deliver as an attached ``.dtlic`` file. """ from __future__ import annotations import argparse import sys import uuid from pathlib import Path # Make ``src.license`` importable when run from the repo root. _PROJECT_ROOT = Path(__file__).resolve().parent.parent if str(_PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(_PROJECT_ROOT)) from src.license import Tier # noqa: E402 from src.license.crypto import encode_blob, sign # noqa: E402 from src.license.features import all_features_for_tier # noqa: E402 from src.license.schema import ( # noqa: E402 License, _utcnow_iso, default_expiry_iso, ) def build_args() -> argparse.ArgumentParser: p = argparse.ArgumentParser( description="Mint a signed DataTools license blob.", formatter_class=argparse.RawDescriptionHelpFormatter, ) p.add_argument("--name", required=True, help="Buyer's full name.") p.add_argument("--email", required=True, help="Buyer's email.") p.add_argument( "--tier", default=Tier.CORE.value, choices=[t.value for t in Tier], help="License tier (default: %(default)s).", ) p.add_argument( "--years", type=int, default=1, help="License lifetime in years (default: %(default)s).", ) p.add_argument( "--key", default=None, help="Override the auto-generated license key (default: random).", ) p.add_argument( "--output", "-o", type=Path, default=None, help="Write the blob to this file (default: print to stdout).", ) return p def main(argv: list[str] | None = None) -> int: args = build_args().parse_args(argv) tier = Tier(args.tier) rid = uuid.uuid4().hex key = args.key or f"DT1-{tier.value.upper()}-{rid[:8]}-{rid[8:16]}" lic = License( name=args.name, email=args.email, license_key=key, tier=tier, features=all_features_for_tier(tier), issued_at=_utcnow_iso(), expires_at=default_expiry_iso(years=args.years), signature="", ) signature = sign(lic.to_canonical_dict()) payload = lic.to_canonical_dict() payload["signature"] = signature blob = encode_blob(payload) if args.output: args.output.write_text(blob + "\n", encoding="utf-8") print(f"Wrote license to {args.output}", file=sys.stderr) else: print(blob) print( f" name: {lic.name}\n" f" email: {lic.email}\n" f" tier: {lic.tier.value}\n" f" key: {lic.license_key}\n" f" expires: {lic.expires_at}", file=sys.stderr, ) return 0 if __name__ == "__main__": sys.exit(main())