The stagehand module (Forge: stagehand-stagehand): one namespace for every ops task the
Puppet Stagehand Console invokes, plus stagehand::console_integration — the idempotent
class that wires a puppetserver primary to the console. This repo is stagehand's
permanent home; puppet-installer vendors it directly by git URL and tag,
pinned in its own vendor.yaml and fetched by its scripts/vendor-modules.sh
(no Puppetfile/r10k involved) — see that repo's own docs for the exact pin.
See RELEASE.md for the CI/security-scan gates and the release
process — read it before cutting a tag.
stagehand::platform_lock— consumes one installer-selected entry fromdata/platform_contract_v1.json, validates exact package ownership by VM role, and applies the matching native APT, DNF/Yum, Zypper, or Windows MSI lock. Puppet Server and PuppetDB receive separate service-specificJAVA_HOMEsettings; PostgreSQL is constrained by major-qualified package families rather than an exact patch build. The class also writes separate root-only desired and observed manifests under/var/lib/stagehand/platform-lockusing a validate, fsync, and rename flow. A failed replacement preserves the prior valid manifest.stagehand::console_integration— apply on the primary to wire it to the console at compile time: cache-aware ENC shim (GET /api/v1/enc/<cert>), self-contained trusted-external command (GET /api/v1/external-data/<cert>→trusted.external.psh.*), thestagehand::hiera_dataHiera Data-Service tier, and the policy-autosign hook (GET /api/v1/autosign/<challenge>). Edits puppet.conf idempotently (guardedpuppet config set, no inifile dep). This is the permanent replacement forhack/tier1-wire.sh. Needs a console service token withhiera:read+nodedata:read.stagehand::compilers—include node_encrypt::certificatesso every compile server can encrypt for any agent (multi-primary). Absorbed from the retiredpuppet_core::compilers.stagehand::console,stagehand::console::docker,stagehand::console::k3s— install, configure, and run the console binary itself (systemd unit, Docker container, or k3s deployment).stagehand::patching— roll thepatchbotexternal fact onto agents so the console's Patching page has data (the pull path; no Bolt push required). Opt-in viastagehand'smanage_patchingparameter.stagehand— anchor class;include stagehandmanages nothing by itself, but itsmanage_patchingparameter (defaultfalse) appliesstagehand::patchingwhen set. This is the one consolidated module (999.12 D-01–D-03) for everything the console needs from Puppet — the puppetserver-integration class, the console-provisioning manifests, and every Bolt task the console dispatches all live here, versioned together. There are no siblingpatchbot/trivy/openscapmodules.
stagehand::secret(Variant[String,Sensitive[String]])→Deferred— branded wrapper overnode_encrypt::secret(); encrypts at compile time with the requesting node's cert, decrypted only on that agent. Absorbed from the retiredpuppet_core::secret. Requires thenode_encryptdependency (declared in metadata; vendored alongside stagehand).
Compliance scanning, scanner lifecycle management, and patching all live
directly in this module — there are no separate trivy/openscap/patchbot
sibling modules to swap in. This repo is the one consolidated stagehand
module (D-01–D-03, phase 999.12): a single pile of Bolt tasks (plus the
console-provisioning manifests) versioned with the console, so the console
dispatches every ops task it needs under one stagehand:: namespace.
stagehand::recert— guarded re-certification (challenge +ext_pp_*identity extensions,input_method: environmentso they pass through).stagehand::r10k_deploy— deploy one environment's code via r10k (pull-based).stagehand::run_playbook— run a pasted Ansible playbook against the node itself viaansible-playbook --connection=local -i localhost,(Bolt pushes this task; no separate Ansible control node exists). Installs Ansible first perinstall_method(auto/package/pip/pipx/wsl/skip) unlessskip, sourcinginstall_ansible.shso a chained install lands in the same run record. Playbook/extra_vars content is delivered only via stdin JSON, written to 0600 temp files, never argv; rejects a playbook that doesn't declare bothhosts: localhostandconnection: local(defense-in-depth — the console API is authoritative). Stdout is a single JSON object:{"install": {...}, "play": {...}}.stagehand::install_ansible— standalone entry point for the same install logicstagehand::run_playbooksources; useful for pre-staging Ansible on a node directly.stagehand::class_enumerate— read-only report of a node's currently-applied Puppet classes, read from the agent's localclasses.txtstate file. Never a live catalog compile, never a set-form Puppet Server call. Ruby (not sh) so it runs identically on Linux and Windows targets.stagehand::scanner_lifecycle— inspect, install, upgrade, or safely uninstall a Trivy or OpenSCAP scanner. A console-instance-scoped ownership marker at/var/lib/stagehand/scanners/<scanner>.jsonis the sole removal authority; a pre-existing external or unmanaged installation is never claimed, overwritten, or removed.stagehand::trivy_scan— scan a node's filesystem with Trivy, normalize the result tocompliance.v1with the bundledfiles/trivy-report.shadapter, and POST it to the console. Installs Trivy wheninstall=true, using a pinned release version with checksum verification (FND-09 / CVE-2026-33634, GHSA-69fq-xp46-6x23).stagehand::openscap_scan— evaluate a node with OpenSCAP (SCAP Security Guide), normalize the result tocompliance.v1with the bundledfiles/scan-report.shadapter, and POST it to the console. Installsopenscap-scanner+scap-security-guidevia the distro-native package manager wheninstall=true.stagehand::inspector_scan— run a Puppet Inspector profile against a node, normalize the result tocompliance.v1with the bundledfiles/inspector-report.shadapter, and POST it to the console. Accepted limit: unliketrivy_scan/openscap_scan, this task does not installpuppet-inspector. The tool is pre-alpha with no published release channel to pin a checksum against, so there is nothing safe to pin an automated install to — adding an unpinned fetch-and-execute install path here would be exactly the supply-chain hole this module's own gate (tools/supplychain/scan.sh) exists to reject. Thepuppet-inspectorbinary must already be present on the target (see theinspector_pathparameter, default/usr/local/bin/puppet-inspector) before this task can run. Every other capability this module ships — Patchbot, Trivy, OpenSCAP — installs and configures itself with no target-side prerequisite; Inspector is the one console-dispatched capability that stops short of that, by design, untilpuppet-inspectorships a pinnable release.stagehand::patch— Patchbot patch-application task (apply all/security-only updates, optional reboot). See 999.12-01-SUMMARY.md for its port history.
lib/puppet/functions/stagehand/hiera_data.rb— thestagehand::hiera_dataHieradata_hashbackend (last-good cache;on_erroruse_cache|continue|fail).templates/*.epp— the shims/configstagehand::console_integrationrenders (client.yaml, ENC shim, trusted-external, autosign hook, hiera.yaml).
stagehand::console (runs the console app) and stagehand::console_integration (wires a
puppetserver primary to it) between them take five sensitive-ish values. None
of them come from an external identity system — they're shared secrets you
invent, except console_binary_source, which is a file you have to obtain
separately. Traced from the actual templates/functions this module renders
(not just the docstrings), here's what each one does and where it has to
match another one:
| Param | On class | What it's for | Where it comes from |
|---|---|---|---|
db_password |
stagehand::console |
Password for the console's own Postgres role/db (psh/psh). Purely local — nothing to do with puppetserver. |
You invent it, e.g. openssl rand -base64 32. stagehand::console creates/syncs the Postgres role to match. |
ingest_token |
stagehand::console |
Doorkey for things pushing data into the console — this module's own stagehand::trivy_scan, stagehand::openscap_scan, stagehand::inspector_scan, and stagehand::patch Bolt tasks POST scan/patch results to the console's ingest API. Becomes PSH_INGEST_TOKEN (templates/console.env.epp). |
You invent it, e.g. openssl rand -hex 32, and give the same value to whatever calls the ingest API. |
dataservice_token |
stagehand::console |
Doorkey for things pulling data out — becomes PSH_DATASERVICE_TOKEN (templates/console.env.epp). |
You invent it — and it must equal stagehand::console_integration's token param below. |
token |
stagehand::console_integration |
The Bearer token puppetserver presents when it calls the console. templates/psh-trusted-external.sh.epp and lib/puppet/functions/stagehand/hiera_data.rb both send Authorization: Bearer <token>; console.env.epp only defines one inbound token for those two APIs (PSH_DATASERVICE_TOKEN) — so this has to be the same string as dataservice_token. |
Same invented string as dataservice_token, reused. |
console_binary_source |
stagehand::console |
The compiled puppet-console binary, staged wherever the target can read it (local path or puppet:///modules/...) — stagehand::console just copies it into place. |
Not produced by this repo. Comes from the separate puppet_console installer repo; stage it yourself (Bolt upload_file, artifact download, package, etc.) before applying stagehand::console. |
Note the asymmetry: psh-enc.sh.epp (the ENC shim) sends no auth header at
all, and psh-autosign.sh.epp authenticates via the CSR's challenge
password, not a Bearer token — so token/dataservice_token only cover 2 of
the 4 integration points (trusted-external data, Hiera Data Service lookups).
Other stagehand::console params are non-secret lifecycle/network knobs:
console_port (default 8443, which port the app listens on),
puppetserver_fqdn (default: applying node's own fqdn — co-located
single-box setup), ensure (present/latest/absent), purge_data
(whether absent also drops the Postgres role/db), and version
(informational only — file's checksum comparison already re-copies a
changed binary regardless).
class profile::psh::all_in_one (
String[1] $console_binary_source,
Sensitive[String[1]] $db_password,
Sensitive[String[1]] $ingest_token,
Sensitive[String[1]] $shared_console_token, # == dataservice_token == console_integration's token
) {
class { 'core_module_pack':
console_url => "https://${facts['networking']['fqdn']}",
token => $shared_console_token,
manage_console => false, # skip the separate puppet_console module
manage_console_app => true, # use stagehand::console instead
console_app_options => {
'console_binary_source' => $console_binary_source,
'db_password' => $db_password,
'ingest_token' => $ingest_token,
'dataservice_token' => $shared_console_token,
},
}
}lookup_options:
'^profile::psh::all_in_one::.+_token$':
convert_to: 'Sensitive'
'^profile::psh::all_in_one::.+_password$':
convert_to: 'Sensitive'
profile::psh::all_in_one::console_binary_source: '/opt/staging/puppet-console'
profile::psh::all_in_one::db_password: 'ENC[PKCS7,...]'
profile::psh::all_in_one::ingest_token: 'ENC[PKCS7,...]'
profile::psh::all_in_one::shared_console_token: 'ENC[PKCS7,...]'stagehand only touches puppetserver through generic, standard mechanisms — an ENC
(node_terminus/external_nodes), a trusted-external-command, a Hiera 5
custom backend, and an autosign executable. Those exist identically across
flavors, so most differences are about what's already on the box, not code:
| Platform | What's different | What to set |
|---|---|---|
| OpenVox | Community fork, no built-in classifier or license server. This is the baseline case the module's defaults assume. | Defaults as-is. |
| Puppet Core (Perforce) | Same AIO install layout (/opt/puppetlabs, /etc/puppetlabs) as OpenVox. Only difference is needing a Puppet Core EULA + Forge API key to install puppetserver itself — a procurement step, not a stagehand setting. |
Defaults as-is once puppetserver is installed. |
| Puppet Enterprise (PE) | PE ships its own node classifier (console node groups) and its own autosign policy machinery, using the same puppet.conf settings (node_terminus/external_nodes, autosign) this module manages. Leaving manage_enc/manage_autosign at their true defaults overwrites PE's classification/signing wiring. |
Decide who's the source of truth. If Stagehand should classify: leave defaults. If PE should stay in charge: set manage_enc => false, manage_autosign => false and keep only manage_trusted_external => true, manage_hiera => true (those two are additive and don't collide with PE). |
console_integration's default paths (confdir/codedir) are the shared
AIO layout and don't need to change across any of the above.
Stagehand is the source owner of data/platform_contract_v1.json.
puppet-installer vendors that file byte-for-byte and selects a role entry;
neither repository may synthesize missing EVRs or use one Puppet version for
all components. The contract keeps puppet-agent, puppetserver, puppetdb,
and puppetdb-termini identities distinct and leaves Java and PostgreSQL on
service-specific major-family guards so routine security patches remain
available.
Repository availability is not platform-lock support evidence. The module-wide
metadata.json support list remains empty until schema-valid live evidence
proves native locking, service runtime, catalog/report, and interruption
recovery for every required identity in an exact contract tuple. The initial
approved release set is deliberately unvalidated; compile-only coverage does
not promote Debian, RedHat-family, SLES, or Windows support claims.
See the Puppet Stagehand operator documentation for the per-role package table,
drift diagnosis, and controlled unlock/upgrade/relock procedure.
./hack/install-stagehand.sh [MODULEPATH_DIR]
bolt task show stagehand::recert
puppet parser validate <modulepath>/stagehand/manifests/console_integration.pp