CI integration
RenMori is a single build-time command that operates on a standard Ren'Py distribution build and requires no changes to the game source. Its input is any directory that contains a game/ subdirectory, which is what the Ren'Py launcher's Build Distributions produces.
This page covers CI usage only. For the full list of command-line options see renmori protect; for renmori.toml settings see Configuration; for the output format, build manifest and exit codes see Output and exit codes.
Basic pipeline flow
protect packs everything under game/ into game/renmori.rmc, transforms the compiled Ren'Py scripts, removes the plaintext originals, writes the loader extension game/renmori.rpe, and emits renmori-manifest.json. verify re-checks a protected build against that manifest (container hash, required files, no plaintext left), which makes it suitable as a CI gate.
With -o the original build is never touched (the copy is protected), so no backup is made. Protecting in place, without -o, first copies the original to a sibling <dir>_bak directory and fails if that directory already exists; --no-backup skips the copy.
The final line of stdout is always a JSON object ({"status": "ok", …} or {"status": "error", …}), and progress goes to stderr. The JSON keys and values stay in English in every locale, so pipeline parsing is unaffected by output styling or language. With --quiet only the JSON is printed. Pipelines should decide success or failure from the exit code; the codes are listed in Output and exit codes.
Non-interactive behavior
RenMori shows confirmation prompts only on an interactive terminal (TTY): the [y/N] confirmation before protecting in place, and the confirmation before downloading the closed plugin. All prompts are skipped when any of the following holds, so a pipeline never hangs waiting for input:
-y/--yesis passed--quietis passed- stdin is not a TTY (the usual case in CI)
Color is disabled automatically off a TTY, and NO_COLOR is honored. The language of the human-facing output follows the system locale and can be pinned with --lang en|zh or the RENMORI_LANG environment variable.
Protection strength and the closed plugin
With strength unset, RenMori defaults to full protection, the strongest strength reported by the closed plugin. If no plugin is available the run fails instead of silently falling back to the baseline format. In CI, choose as needed:
When a plugin is needed, RenMori looks for the path given by --plugin or the RENMORI_PLUGIN environment variable, then the local cache, and finally downloads the plugin from the public release, verifies it against SHA256SUMS, and caches it. In a non-interactive environment the download proceeds without confirmation. See Closed plugin for the full description.
Caching the plugin in CI
The plugin cache directory is keyed by the plugin release tag:
Persisting this directory between CI runs avoids downloading the plugin on every build. In GitHub Actions, add a cache step before the Protect step, with a cache key that matches the plugin release tag RenMori uses:
The following environment variables give further control over how the plugin is obtained:
Key management
The [keys] section applies to the baseline format only. With the closed plugin, the plugin derives its own keys and does not read these settings.
By default protect generates a fresh random master key and an independent script key for every build and embeds them in the loader extension (a baseline placeholder; the closed runtime derives keys at runtime instead). To manage keys yourself, name the environment variables that hold them in renmori.toml; each key is 64 hex characters (32 bytes):
Populate these variables from your CI secret store and never commit keys to the repository. If a named variable is unset, RenMori falls back to a random key and reports it.
RenMori reads only the environment variables named in [keys]. Without a [keys] section in renmori.toml, setting RENMORI_MASTER_KEY and similar variables in CI has no effect, and every build still uses random keys.
Output is not byte-reproducible across builds even with fixed keys (each file uses a random salt), by design. Pipelines should assert on the result of verify and on the file list in the manifest, not on the container hash across builds.
GitHub Actions example
examples/github-actions-protect.yml in the repository demonstrates the complete flow: build the game, run renmori protect and renmori verify, then upload the protected distribution build as an artifact.
To use it:
- Copy the file to
.github/workflows/protect.ymlin your project - Replace the Build the game step with your actual Ren'Py build command; it must produce a directory that contains a
game/subdirectory - Adjust the triggers as needed; the example runs on manual dispatch and on pushes of
v*tags - Optionally add the plugin cache step shown above so the plugin is not downloaded on every run
RenMori runs only on the CI runner (the build environment); the protected runtime ships inside the game, and players need no separate Python installation. The example runs at the default strength and therefore obtains the closed plugin on the runner. The key variables in the Protect step take effect only with the baseline format and only when the [keys] section of renmori.toml names them, as described under Key management above.
End-to-end check against a real engine (optional)
tools/verify_in_renpy.py protects a sample game and confirms, inside a real Ren'Py SDK, that every asset decrypts byte-perfectly, that Ren'Py scripts are transformed yet still parse, and that injected scripts are refused. Point it at an SDK and a game; the check is skipped where no SDK is available.