core.cmd
Run a shell string through `sh -c`: pipes, redirects, glob, substitutions.
core.cmd runs a shell string through `sh -c` — pipes, redirects, glob, and substitutions are available. The module is TRUSTED-ONLY: the string goes to the shell without escaping, so any interpolation inside it is executed by the shell as code, and its source must be the Destiny author, not external input. By default the step reports changed=true; you can lower it to a no-op with the creates / unless / onlyif guard parameters.
Requirements
- Rootnot required
- Side
soul-side - Collection
soulstack.exec - Category
exec
exec_subprocessStates
core.cmd.shell — Run a shell string through `sh -c`.
Always by default (the "run" verb).
A guard creates / unless / onlyif that fired held the step at no-op.
Parameters
| Param | Type | Required / default | Description |
|---|---|---|---|
cmd | string | required | Shell string, executed via `sh -c`. |
cwd | string | optional | Working directory of the process. |
env | map<string> | optional | Additional environment variables (KEY: VALUE). |
creates | string | optional | Idempotency: if the file exists, the step is skipped (changed=false). |
unless | string | optional | Idempotency: shell command; exit=0 → skip. |
onlyif | string | optional | Idempotency: shell command; exit≠0 → skip. |
Example — creates-guard: install is skipped if the binary is already in place
- name: Install exporter binary module: core.cmd.shell params: creates: /usr/local/bin/redis_exporter cmd: "install -m 0755 /tmp/build/redis_exporter /usr/local/bin/redis_exporter"Example — Version-aware unless: reinstall when the binary version differs
- name: Install node_exporter binary module: core.cmd.shell register: node_exporter_bin params: unless: "test -x /usr/local/bin/node_exporter && /usr/local/bin/node_exporter --version 2>&1 | grep -qF 'version 1.9.0 '" cmd: "install -m 0755 /tmp/node_exporter/node_exporter /usr/local/bin/node_exporter"Output
| Field | Type | Description |
|---|---|---|
stdout | string | |
stderr | string | |
exit_code | number |
Notes
- Guard parameters are checked in the order creates → unless → onlyif; the first one that fires skips the command (changed=false, output { skipped: true, reason }).
- A non-zero exit of the main command does not by itself make the step failed — that is decided by failed_when: in the scenario.
- creates only catches the existence of a path and does not notice a version upgrade (same path → no-op even with stale contents); for version-aware idempotency use unless with a --version check.
Security & defaults
- TRUSTED-ONLY is the main invariant: cmd goes to `sh -c` without escaping, and any interpolation of an untrusted value (CEL-render, register.*, soulprint.*, input.*) becomes a shell injection through the metacharacters $, `, |, &, ;, >, <, *.
- The source of the cmd string must be the Destiny/scenario author, not external input; the unless / onlyif guard commands also go through `sh -c` and are subject to the same rule.
- The module does not declare run_as_root and does not raise permissions — the command runs with the soul agent process's privileges; in practice the agent runs as root, so `sh -c` also runs as root, which only raises the cost of an injection.
- Where shell semantics are not needed, use core.exec.run: the argv form passes the value as a separate token and metacharacters are not interpreted.