The rpc pack (packs:rpc)
packs:rpc
The packs:rpc entitlement delivers two things as one grant: the rpc
YAML action pack — remote execution and file transfer over SSH, driven
by rpcu, the standalone LimePoint transport tool — and the
transport-provider family those actions broker remote-host access
through (providers, grants and scope bindings). One entitlement natively
grants both; there is no separate feature flag, and bundles compose it in
at licence minting — packs:mintpress today, and every licence granting
packs:infrastructure is minted with packs:rpc alongside. See
entitlements for the gate's exact refusal shapes, and
transport providers for the brokering machinery
itself.
The pack mounts like any other action pack — a packs: entry in the
template's .opschain/packs.yaml naming the pack remote and a pinned
revision (opschain docs getting-started/2-4-using-packs) — and its
signed manifest requires packs:rpc, so an unentitled mount refuses with
the named pack refusal before any action runs.
The shipped actions
Every action is an ordinary YAML action invoked as rpc:<name>, so it
composes with uses:, workflows and triggers like any other. Family by
family:
Execution
| Action | Purpose |
|---|---|
rpc:exec | Run a command over SSH — streamed logs; exit_code, stdout/stderr tails and duration_ms published as step outputs. Non-zero exit fails the step unless allow_failure: true. Key inputs: command (required), target, sudo_user/sudo_method, hops (OpenSSH -J-form jump chain), timeout, capture. |
rpc:exec_via | rpc:exec with the jump chain required — auditable bastion use. |
rpc:facts | Native host facts over SSH (no remote agent), every fact a step output. |
File transfer
| Action | Purpose |
|---|---|
rpc:put / rpc:get | Copy a file or whole tree (recursive: true) between the step workspace and the target — modes preserved, never 0666. |
rpc:sync | rsync-ish tree synchronisation with a checksum level and the guarded delete/delete_under; a sync report is published. |
Surgical remote edits — all three are idempotent by construction (a
re-run reports changed: false and writes nothing), write atomically with
a timestamped backup published as the backup output, and record counts
only, never file contents:
| Action | Purpose |
|---|---|
rpc:file_edit | Config edits on the target in three families — key=value, line-in-file (line_ensure/line_match/line_absent), ini-section (ini_section/ini_set/ini_rm). For shared, distro-owned files where one key must change and hand edits must survive; a platform-owned file is rendered whole and rpc:put instead. |
rpc:zip_edit | Tokenise members inside a remote zip archive (open-edit-rewrite, no target-side zip tooling); members matching include/exclude globs get tokens (TOKEN=VALUE pairs) replaced. |
rpc:tokenise | Tokenise a remote directory tree (sync-down, transform, sync-up) — only changed files re-upload. |
Protocol clients
| Action | Purpose |
|---|---|
rpc:sql | Oracle SQL through the session — go-ora over TNS by default (third-host listeners via the jump machinery), or via: sqlplus on the target for bequeath/OS-auth connects and sqlplus scripts. A missing sqlplus is refused by name, never a raw command-not-found. |
rpc:http | HTTP client through the session — cookie jar, selections, form round trips, PUT uploads from a runner-local file, per-client TLS posture; console-only endpoints via the jump machinery. |
Tooling
| Action | Purpose |
|---|---|
rpc:ensure | Makes the pinned rpcu binary available on the runner: PATH (image-baked) → the runner cache → a verified download from rpc.binary_url, checked against the pack's pinned sha256 before use. A public URL is never a default. |
Two input conventions run through the whole catalog:
- The connection profile. Every action's connection inputs default to
the target node's
rpc.*properties —rpc.target,rpc.hops,rpc.tofu_store,rpc.binary_url,rpc.connect_timeout,rpc.keepalive— so a workflow step can be justwith: { command: "…" }; an explicitwith:value overrides the profile. Host keys are trust-on-first-use into the property-backedrpc.tofu_store(first contact logged loudly, a changed key refused);known_hostsselects a strict policy instead. - Secret-valued inputs (
db_password,password,token,secret_value) are vault-resolved secret inputs handed torpcuby env-var name — never argv, masked in logs, absent from the step record.
How actions reach remote hosts
Credentials never live in node properties. An rpc action's wrapper reads a
one-shot session from the masked OPSCHAIN_TRANSPORT_SESSION environment
variable and pipes it to rpcu --credentials-stdin: at dispatch the
engine resolves the transport provider governing the step's target,
enforces the run principal's transport grant (a miss fails the step by
name), resolves the provider's credential reference with platform
authority and mints the session bundle — the resolved key or password for
an ssh-static provider, a short-lived CA-signed certificate plus
ephemeral key for ssh-ca. The whole brokering model — provider kinds,
grants, scope bindings, the dispatch precedence and the events it lands —
is transport providers; the runner-side
environment contract is on opschain docs runner-executors. Run
standalone outside the platform, the wrappers fall back to the ambient
RPCU_KEY_DATA / RPCU_PASSWORD / RPCU_SUDO_PASSWORD channel.
The tools:rpcu composition and the licence hand-off
The rpcu binary is itself licence-gated, offline: its user verbs refuse
by name without a LimePoint-signed licence document granting tools:rpcu
— and packs:rpc or packs:infrastructure satisfy the same gate.
Minting a licence that grants packs:rpc, packs:infrastructure or
packs:mintpress composes tools:rpcu into the document alongside, so
the tool's own gate holds whichever spelling the estate's licence carries.
The platform hands the licence to the tool the same way it hands the
session: a step whose command references OPSCHAIN_LICENCE_DOCUMENT gets
the estate's applied licence envelope by value in that variable —
picked as the latest-expiring applied licence granting tools:rpcu,
packs:rpc or packs:infrastructure, masked out of logs like any minted
secret. The wrappers materialise it into a private temp file for rpcu's
offline dial and remove it on exit; the document is never embedded in the
pack. When no applied licence grants the tool, the variable is simply not
set — the ambient RPCU_LICENCE fallback stays in charge and rpcu's own
gate streams its named refusal to the step log. A local (--local) run
makes the same hand-off from the licence files on disk.
Lapse behaviour
The polite-lapse posture, uniformly: on entitlement lapse, creating a transport provider or enabling a disabled one, creating or directly approving a transport grant, and declaring a scope binding each refuse with the typed entitlement refusal naming the pack — while stored providers, grants and bindings keep serving dispatch, reads and the test verb stay up, and disabling, deleting, rejecting, revoking and binding deletes stay open so a lapsed estate can always unwind authority. Running work is never stopped by the pack gate alone.
Composition with other packs
packs:infrastructuresatisfies everypacks:rpcgate — it bundles the rpc engine, every licence granting it is minted withpacks:rpcalongside, and the gate itself backstops a licence minted without the bundle. The reverse never holds:packs:rpcalone grants neither the infrastructure pack's host-targeted dispatch (host_refre-pointing at registered infrastructure host nodes) nor its provision hand-over.packs:mintpressbundlespacks:rpcat licence minting, so MintPress customers drive the rpc actions and the transport family under their existing grant.
Where the rest lives
- Transport providers, grants and bindings — the brokering component this pack's actions ride.
- Entitlements — the gate vocabulary and refusal shapes.
opschain docs cli/admin-transport-providersandopschain docs cli/admin-transport-grants— the admin CLI families.opschain docs getting-started/2-4-using-packs— mounting a pack onto a template.opschain docs runner-executors— the step environment contract (OPSCHAIN_TRANSPORT_SESSION,OPSCHAIN_LICENCE_DOCUMENT).