Packaging & compiled apps
py-rizmi is tested end-to-end inside compiled binaries. Here's what matters.
Nuitka (recommended)
nuitka --standalone --onefile \
--include-package=py_rizmi \
--output-dir=dist main.py
# PyQt6 toolkit apps: add --enable-plugin=pyqt6
sqlite3is stdlib — no extra data files to declare.- Compiles your embedded public key and fingerprint constants into native C. Patching them means rebuilding the binary — a far higher bar than editing an archive.
- An e2e test in the repo builds a Nuitka standalone and runs the whole activate → tamper → detect cycle inside it on every release.
PyInstaller
pyinstaller --onefile main.py
# plain import works; no hidden-import needed for py_rizmi core
PyInstaller archives are trivially extractable — bytecode and embedded constants can be read and patched with off-the-shelf tools. Functional, but choose Nuitka when licensing strength matters.
The one rule that makes both work
Never derive state paths from __file__. Nuitka onefile extracts to a temp dir wiped at exit; PyInstaller's __file__ points inside _MEIPASS. py-rizmi already handles this: writable state always resolves to the platformdirs user-data location:
| OS | State DB location |
|---|---|
| Linux | ~/.local/share/<AppName>/state.db |
| macOS | ~/Library/Application Support/<AppName>/state.db |
| Windows | %LOCALAPPDATA%\<AppName>\state.db |
What lives where
| Compiled into the binary | state.db (writable) | |
|---|---|---|
| Vendor public key | ✓ as a constant + pinned fingerprint | never |
| Private key | never (your issuing machine only) | never |
| Licenses / trial keys / clock marks | — | HMAC-verified rows + fallback file |
Release checklist
rizmi doctor run -a MyApp -P pub.pem --fingerprint <hex>passes on a clean VM- First run starts a trial; second run continues it (not restarted)
- System clock rolled back → blocked with
clock_tampering state.dbdeleted → protection intact via fallback marks- License issued for another machine → rejected
hwid_mismatch