Repository navigation
Extending Writing Lua Plugins
Prev: Choosing an Extension Model | Up: Extending ReqPack | Next: Building Registry Entries
Lua plugins are the main extension point for ReqPack.
They implement package-manager behavior while ReqPack provides config, planning, audit integration, output handling, and remote execution.
This page describes normal wrapper or proxy plugins loaded through LuaBridge, not hook scripts inside native .rqp packages.
Local plugin layout:
plugins/
<plugin-id>/
<plugin-id>.lua
bootstrap.lua # optional
Important:
- plugin implementation must expose global
plugintable, - optional bootstrap hook is global
bootstrap()function inbootstrap.lua, notplugin.bootstrap().
Examples in this repo:
plugins/dnf/dnf.luaplugins/maven/maven.luaplugins/java/java.luaplugins/sys/sys.lua
ReqPack validates the Lua plugin contract at load time. These methods are required:
function plugin.getName() end
function plugin.getVersion() end
function plugin.getRequirements() end
function plugin.getCategories() end
function plugin.getMissingPackages(packages) end
function plugin.install(context, packages) end
function plugin.installLocal(context, path) end
function plugin.remove(context, packages) end
function plugin.update(context, packages) end
function plugin.list(context) end
function plugin.search(context, prompt) end
function plugin.info(context, packageName) endOptional methods:
function plugin.init() end
function plugin.shutdown() end
function plugin.outdated(context) end
function plugin.resolvePackage(context, package) end
function plugin.resolveProxyRequest(context, request) end
function plugin.getSecurityMetadata() endOptional static data:
plugin.fileExtensions = { ".rpm" }plugin.fileExtensions is metadata table, not function.
plugin = {}
function plugin.getName()
return "Example Manager"
end
function plugin.getVersion()
return "1.0.0"
end
function plugin.getRequirements()
return {}
end
function plugin.getCategories()
return { "Example" }
end
function plugin.getMissingPackages(packages)
return packages
end
function plugin.install(context, packages)
context.tx.begin_step("install example packages")
local result = context.exec.run("example-pm install ...")
if not result.success then
context.tx.failed("example install failed")
return false
end
context.events.installed(packages)
context.tx.success()
return true
end
function plugin.installLocal(context, path)
return false
end
function plugin.remove(context, packages)
return true
end
function plugin.update(context, packages)
return true
end
function plugin.list(context)
return {}
end
function plugin.search(context, prompt)
return {}
end
function plugin.info(context, packageName)
return {}
endReqPack exposes a rich plugin context.
context.plugin.id
context.plugin.dir
context.plugin.script
context.plugin.bootstrapcontext.flags
context.host
context.proxy
context.repositoriescontext.log.debug("...")
context.log.info("...")
context.log.warn("...")
context.log.error("...")context.tx.status(42)
context.tx.progress(50)
context.tx.begin_step("resolve dependencies")
context.tx.commit()
context.tx.success()
context.tx.failed("something went wrong")context.tx.progress(...) can also accept a richer payload with byte counters and rates.
context.events.installed(payload)
context.events.deleted(payload)
context.events.updated(payload)
context.events.listed(payload)
context.events.searched(payload)
context.events.informed(payload)
context.events.outdated(payload)
context.events.unavailable(payload)These events feed ReqPack's display and structured output layers.
local result = context.exec.run("dnf repoquery curl")
local tmpDir = context.fs.get_tmp_dir()
local ok = context.net.download(url, destination)
context.artifacts.register({ type = "file", path = "/tmp/out" })Important runtime boundaries:
-
context.fsin Lua plugins currently exposes temp-directory helper, not general copy/mkdir helpers. -
context.net.download(...)returns success boolean. -
context.artifacts.register(...)records artifact payload for ReqPack; shape is plugin-defined JSON-like table. - if you need richer filesystem helpers for install/remove hooks, that is native
rqphook runtime, not normal Lua plugin runtime.
There is also a global namespace:
local result = reqpack.exec.run("command -v dnf >/dev/null 2>&1")
local host = reqpack.hostUse context.exec.run(...) inside action methods when possible, because it carries the current item identity and display correlation.
Relevant fields:
actionsystemnameversionsourcePathlocalTargetflagsdirectRequest
actionsystempackagesflagsoutputFormatoutputPathlocalPathusesLocalTarget
Useful fields include:
-
name,packageId,version,latestVersion -
status,installed -
summary,description -
homepage,documentation,sourceUrl,repository -
channel,section,packageType,architecture,license -
dependencies,optionalDependencies,provides,conflicts,replaces,binaries,tags -
extraFieldsfor additional key/value output
You do not need to fill every field. Fill the ones your ecosystem can provide reliably.
ReqPack uses getMissingPackages() during planning to avoid redundant work.
Good plugin behavior:
- return only packages that still need action,
- for installs: packages not already present,
- for removes: packages that are actually present,
- for updates: packages with available newer versions.
If this function is lazy and always returns everything, ReqPack still works, but planning quality and UX get worse.
If your plugin can resolve a package into an exact version, implement resolvePackage().
This helps with:
- vulnerability matching,
- SBOM exports,
- explicit package identity,
- better audit decisions when users do not specify exact versions.
Without it, unresolved-version policy may block installs or reduce audit fidelity.
When you add a new ecosystem, this is the practical minimum validation path:
- implement
resolvePackage()if exact version lookup is possible, - return
osvEcosystemfromgetSecurityMetadata()or configuresecurity.osvEcosystemMap, - run audit with a local feed path,
- run SBOM export for explicit packages and installed packages.
Useful commands:
ReqPack audit <system> <package> --osv-feed ./test-data/osv --osv-refresh always
ReqPack audit <system> --strict-ecosystem-mapping
ReqPack sbom <system> --format jsonIf audit fails with ecosystem-mapping or unresolved-version errors, fix those before calling the plugin production-ready.
Proxy plugins should not implement real package-manager behavior directly. They should transform the request and hand it to a real target system.
Minimal shape:
function plugin.resolveProxyRequest(context, request)
return {
targetSystem = "maven",
flags = request.flags,
packages = request.packages,
}
endRules:
- do not return both
packagesandlocalPath - do not resolve to yourself
- stay within configured
context.proxy.targetswhen present - if you return
flags, they replace request flags for next resolved request
If you want better trust and security behavior, implement getSecurityMetadata().
Example:
function plugin.getSecurityMetadata()
return {
role = "package-manager",
capabilities = { "exec", "network" },
ecosystemScopes = { "rpm" },
writeScopes = {
{ kind = "temp" },
{ kind = "user-home-subpath", value = ".cache/dnf" },
},
networkScopes = {
{ host = "api.osv.dev", scheme = "https", pathPrefix = "/v1" },
},
privilegeLevel = "sudo",
osvEcosystem = "RPM",
purlType = "rpm",
versionComparatorProfile = "rpm-evr",
versionTokenPattern = "[A-Za-z0-9._+-]+",
versionCaseInsensitive = false,
}
endThis becomes especially important when security.requireThinLayer = true.
- Keep the plugin thin. Let the real package manager do real package-manager work.
- Emit events and transaction steps. They improve UX, remote output, and logs.
- Implement
installLocal()if your ecosystem supports local artifacts. - Implement
resolvePackage()as soon as exact version resolution is possible. - Return realistic
PackageInforecords.infoandoutdatedshould not be placeholders in mature plugins. - Prefer deterministic shell commands and parse outputs conservatively.
- Use
plugin.fileExtensionsif local-file inference makes sense. - Test plugin through actual
ReqPackcommands because runtime contract is behavioral, not only structural.
Prev: Choosing an Extension Model | Up: Extending ReqPack | Next: Building Registry Entries
- User Guide
- Getting Started
- Command Reference
- Configuration
- Configuration Reference
- Security, Audit, and SBOM
- Output and Report Formats
- Remote Mode
- Remote Protocol Reference
- Using Native
rqpPackages - Troubleshooting