The infrastructure pack (packs:infrastructure)
packs:infrastructure
Host-centric remote operation: the infrastructure:* actions run
commands, move files and trees, gather facts and close the estate
membership loop against registered infrastructure host nodes (see
infrastructure nodes) — uniformly across
cloud-handed-over, virtualised and plain on-premise hosts. Every action
targets a host by host_ref, the host node's path: the dial engine is
rpcu (the rpc pack's), and credential custody is the platform's —
connection secrets live in transport
providers, never in node properties (the
registry refuses credential-shaped property keys by name). Everything on
this page requires the packs:infrastructure licence entitlement
(see entitlements), enforced at every run-creation
door through the pack's signed manifest.
The triage line: a host you operate by name is an infrastructure node —
reference it with host_ref and use infrastructure:host_*. The
rpc pack's rpc:* actions remain the lane for ad-hoc
targets carried in an asset's own rpc.* properties; this pack exists
to end passing raw user@host strings around a workflow. The host node
is the single home for the connection profile, discovered facts and
lifecycle state.
The shipped actions
| Action | Does |
|---|---|
infrastructure:host_exec | Runs a command on the referenced host — streamed logs, typed exit_code/stdout/stderr result; a non-zero remote exit fails the step unless allow_failure: true. Sudo shaping via sudo_user/sudo_method. |
infrastructure:host_put / infrastructure:host_get | Copies a file or whole tree (recursive: true) between the step workspace and the host (src/dest; put adds mkdirs and an explicit mode — never 0666). |
infrastructure:host_sync | Rsync-ish tree sync (direction: up pushes the workspace tree, down pulls the remote one) with checksum, exclude patterns and the guarded delete/delete_under. |
infrastructure:host_facts | Gathers native host facts over the session and records them on the host node through the facts hand-over (freshness stamped); every fact is also a step output. record: "false" gathers without the node write. |
infrastructure:host_register | The membership hand-over: registers (or updates) the host node's estate membership under a parent path with code, name, address, fqdn and a properties document. |
infrastructure:host_deregister | Closes the host node's estate membership — the soft-close posture; re-registering the same code revives the same node. |
infrastructure:ensure | Makes the pinned rpcu binary available on the runner: PATH, then the runner cache, then a download from an explicit mirror url verified against the pack-pinned sha256. A public URL is never a default; the other actions call it implicitly. |
The dialing actions share the connection inputs: host_ref (required),
with target, hops and tofu_store defaulting from the host node's
own rpc.target, rpc.hops and rpc.tofu_store properties via
estate(inputs.host_ref) — an explicit with: value overrides.
collect_facts: "true" on any dialing action adds the opt-in facts
piggyback: facts over the same admitted session after the primary
command, recorded as its own sub-result, never failing the primary.
Fan-out over an estate scope rides the filtered estate() enumerate
(opschain docs expressions):
- type: change
name: patch_all
for_each: ${{ estate('/projects/acme/environments/prod', "type == 'host'") }}
as: host
target: /projects/acme/environments/prod/assets/fleet
action: infrastructure:host_exec
with:
host_ref: '${{ host.node_path }}'
command: dnf -y update openssl
How a step reaches its host
A host_ref input re-points the whole transport seam at the referenced
host node. At dispatch the engine resolves the reference and refuses
every miss by name — an unresolved expression, an unknown node, a
non-host node, and a deregistered or destroyed host each fail the step
loudly, never silently falling back to the run's target. For an admitted
host, the provider governing the host is selected (the host's own
rpc.provider_ref property, else the deepest-wins scope binding at the
host's path, else the single enabled provider), the caller's transport
grant is enforced against the host node's path — the grant covers
the host, not the declaring scope — and a one-shot rpcu credentials
document is minted into the masked OPSCHAIN_TRANSPORT_SESSION
environment variable the wrapper scripts pipe to rpcu --credentials-stdin: never argv, never a file, never node properties.
Every admitted session lands the transport session event; grant refusals
land their own warn events. The mechanics — providers, bindings, grants,
caps — are the rpc pack's transport engine: see
transport providers and
the rpc pack.
The membership hand-over
Any action that makes a machine exist — a cloud instance, a virtualised
guest, a hand-built box — closes its loop by calling
infrastructure:host_register, so the machine becomes estate content:
visible in the infrastructure screens, referenceable by host_ref,
governed by path RBAC. When the machine leaves the estate,
infrastructure:host_deregister closes its membership — the node is
soft-closed with full history retained, and registering the same code
again revives the same object. The hand-over asserts estate membership
only: it never marks a node provisioned or destroyed — those
operational states are reserved for the integration that performs the
operations. It rides the ordinary opschain infrastructure verbs
(opschain docs cli/infrastructure), no separate API surface and no
credentials involved.
Entitlement composition
packs:infrastructure never travels alone:
- Minted as a bundle. Every licence granting
packs:infrastructureis minted withpacks:rpcandpacks:commonalongside, so the transport-provider family and the baseline pack surfaces arrive with it. - The gate backstop. A
packs:infrastructuregrant satisfies everypacks:rpcgate, so even a licence minted without the bundle still drives the pack's transport engine. The reverse never holds —packs:rpcalone does not grant the infrastructure pack. - The tool grant. Minting a licence granting
packs:infrastructure(as withpacks:rpcandpacks:mintpress) also composes intools:rpcu, the standalonerpcubinary's own offline entitlement;rpcu's gate acceptstools:rpcu,packs:rpcorpacks:infrastructure. At dispatch the engine hands the applied licence envelope by value to exactly the steps whose command referencesOPSCHAIN_LICENCE_DOCUMENT(the pack wrappers' licence-env channel), and leaves it unset when no applied licence grantsrpcu.
Enforcement is the signed-pack funnel: the pack's repository-root
manifest declares requires: packs:infrastructure, verified against the
product keyring and checked at every door that creates a run using the
pack — direct changes, the templated lane including workflow-nested
changes, and retries. An unentitled create refuses with the named
refusal (pack, entitlement, remedy); see
entitlements for the gate idiom.
On licence lapse
The gate sits at run creation only, so a lapse is polite:
- Creating a run that uses the pack refuses with the named entitlement refusal; already-created, in-flight runs finish undisturbed (dispatch is deliberately not re-gated). A retry re-gates on the run's frozen pack set, so a lapsed entitlement refuses it.
- The host-node registry is base platform, not pack-gated: registered hosts, their facts, reads, updates and deregistration all stay available for inventory and lapse cleanup — see infrastructure nodes.
- Transport providers and grants follow their own polite-lapse posture
under
packs:rpc(which this pack's grant satisfies): stored records keep serving, only create/enable writes gate.
Where the core documentation lives
- The host node kind — attributes, lifecycle, registry semantics: infrastructure nodes.
- The transport engine — providers, bindings, grants, session minting: transport providers and the rpc pack.
- Declaring packs in a template and the pack funnel:
opschain docs getting-started/2-4-using-packs. - The
packs:infrastructureentitlement, composition and refusals: entitlements.