v2.1 Python ≥ 3.12 MIT Offline-first · RS256 · HWID-bound

Licensing for Python apps,
without the server.

RSA-signed licenses bound to each machine, tamper-evident local state in one SQLite file, trials that survive clock rollback — and a CLI that answers support tickets for you. Works compiled (Nuitka / PyInstaller), works offline.

Everything a licensing SDK should have shipped.

py-Rizmi covers the whole lifecycle: keys → issue → activate → enforce → revoke → diagnose. No accounts, no cloud dependency, no per-seat fees.

🔑Signed & hardware-bound

RS256 JWT licenses bound to an OS-level machine ID. A license issued for machine A fails on machine B with a precise error code.

🗄️One SQLite file

Keys, licenses, trial state and clock marks live in a single per-app database. Every row is HMAC-verified against a machine+app-bound key before use — editing the data is always detected.

⏱️Anti-rollback clock guard

A persisted high-water mark catches "wind the clock back and keep the trial". Redundant copies mean deleting one file (or the whole DB) resets nothing.

🎁Trial periods built in

The app issues itself a self-signed trial on first run. Deleting the trial restarts nothing; buying a license supersedes the trial mid-evaluation.

🧩3-line integration

LicenseGate wires validation, trial, activation and watchdog behind one object with truthy statuses your UI can render directly.

📦Compiled-app ready

Tested end-to-end inside Nuitka standalone binaries. Paths never derive from __file__; constants compile into C where patchers can't reach casually.

🚫Revocation lists

Publish a signed list of revoked license IDs; validators reject revoked installs even fully offline. Bad signatures are rejected loudly, never silently ignored.

🩺rizmi doctor

Machine ID readable? DB intact? Clock sane? Key pinned correctly? One command prints the checklist your support team was going to ask for anyway.

🖥️In-app activation UI

Your app hosts the paste box or file picker; py-rizmi runs the full validation chain before anything is stored. Two methods: token or file.

Licensing in three lines

After rizmi init MyApp generates your keys and snippet:

from py_rizmi import LicenseGate

gate = LicenseGate(app_name="MyApp", public_key=KEY,
                   expected_fingerprint=FINGERPRINT, config_dir=cfg)

status = gate.start()          # first run starts the trial automatically
if not status:
    print(status.message)      # expired / tampered / wrong machine…
Full integration guide →

Honest about the threat model

Local licensing is tamper-evident, not tamper-proof. A determined reverse engineer with a debugger wins eventually — that is true of every offline scheme. py-Rizmi raises the bar against casual-to-moderate piracy, which is where the real volume is, and tells you exactly what it can and cannot stop. For stronger guarantees, pair it with online activation in a future phase.