Deploy a Signalman runner
WS6 M9 closes the M3.5 deferral. Five transports, one verb: each takes the runner binary URL + registration config + a transport- specific addressing block and bootstraps a runner on the target. By default the verb waits for the runner to heartbeat before declaring success — turning "successful bootstrap" into "successfully running."
Which transport to use
| Transport | When | Auth model | Verification |
|---|---|---|---|
script |
Operator wants to run the install themselves (air-gapped, custom bootstrap, audit trail). | None (no remote auth) | Operator runs; verification deferred. |
ssh |
Linux / macOS target the operator has SSH-key access to. | Operator-supplied IdentityFile. |
Heartbeat poll. |
winrm |
Windows target with WinRM configured (Enable-PSRemoting). |
Operator-supplied username + password. |
Heartbeat poll. |
docker |
Containerised runner on a local or remote Docker daemon. | Docker context (operator pre-configures). | Heartbeat poll. |
cloud |
"Give me a runner on AWS / Azure in " — provisions a fresh VM then bootstraps via ssh / winrm. | Cloud creds (env or per-org) + inner ssh/winrm creds. | Heartbeat poll. |
Defaults to ssh is the most common case for "I have a host I want a
runner on." cloud is the "and I don't even have the host yet" case.
Common inputs (every transport)
binary_url— HTTP(S) URL to the runner binary. Typical shape:https://<registry>/v1/blobs/sha256:<hash>from a@signalman/registrypush. Any URL the remote cancurlworks.binary_sha256(optional) — 64 hex chars. When set, the transport verifies after download and refuses to install on mismatch. If the URL is a registry blob URL, the sha is extracted from the path even when this field is omitted.control_plane_url— URL the runner reports to.token— API key the runner authenticates with. Mint viasignalman_api_key_createfirst; the key is returned ONCE.worker_name— friendly name; surfaces in the runners table.wait_timeout_ms(optional) — heartbeat-verification budget. Default 60000. Set to 0 to fire-and-forget.
How to invoke
script (operator-driven install)
{
"binary_url": "https://reg/v1/blobs/sha256:abc...",
"control_plane_url": "http://control-plane:7777",
"token": "tok-xyz",
"worker_name": "operator-mac",
"transport": { "kind": "script", "os": "macos" }
}
Returns bootstrap.script containing the bash/pwsh body. Operator
runs it on the target. Verification is skipped (operator may not
run the script immediately).
ssh
{
"binary_url": "...",
"control_plane_url": "...",
"token": "...",
"worker_name": "linux-runner-01",
"transport": {
"kind": "ssh",
"host": "deploy@host-01.lan",
"identity_path": "/home/operator/.ssh/signalman-deploy",
"port": 22,
"service_manager": "systemd"
}
}
service_manager is systemd (default), launchd, or none.
launchd writes a per-user plist; none installs the binary +
config but doesn't auto-start.
winrm
{
"binary_url": "...",
"control_plane_url": "...",
"token": "...",
"worker_name": "win-runner-01",
"transport": {
"kind": "winrm",
"host": "win-01.corp",
"username": "CORP\\deploy",
"password": "...",
"use_ssl": true
}
}
Enable-PSRemoting -Force must be set on the target ahead of time.
The transport registers a Windows service named
SignalmanRunner_<workerName>.
docker
{
"binary_url": "https://reg/v1/blobs/sha256:abc...",
"control_plane_url": "...",
"token": "...",
"worker_name": "docker-runner-01",
"transport": {
"kind": "docker",
"image": "ghcr.io/operator/signalman-runner:0.4.x",
"context": "default",
"extra_env": { "EXTRA_KEY": "VALUE" }
}
}
For docker, binary_url is informational — the runner binary lives
inside the operator's image. The transport runs docker pull →
docker rm -f (idempotent) → docker run -d with the registration
env vars passed in. Remote daemons via docker context create.
cloud (provision + bootstrap in one call)
{
"binary_url": "https://reg/v1/blobs/sha256:abc...",
"control_plane_url": "...",
"token": "...",
"worker_name": "aws-runner-checkout",
"transport": {
"kind": "cloud",
"provider": "aws",
"region": "us-east-1",
"instance_type": "t3.medium",
"image_ref": "ami-0123456789abcdef0",
"name": "scenario-checkout-runner",
"os_family": "linux",
"inner_ssh_identity_path": "/home/operator/.ssh/aws-deploy",
"ttl_minutes": 60
}
}
The cloud transport:
- Provisions a fresh VM (cost-reaper owns its TTL).
- Polls for a public IP (up to 2min).
- Dispatches to
ssh(linux) orwinrm(windows) using the inner credentials supplied.
If the inner bootstrap fails AFTER provisioning, the VM stays alive
(operator can terminate manually via signalman_cloud_terminate or
let the reaper handle it).
Common errors
| Error pattern | Cause | What to do |
|---|---|---|
exec timeout |
Remote unreachable (firewall, wrong host, ssh server down). | Verify with manual ssh -i <key> <host> or Test-NetConnection. |
exited with 255 (ssh) |
SSH auth or host-key failure. | Pre-populate known_hosts; verify the identity file path. |
pull exited with 1 (docker) |
Image not found / auth required. | Verify docker pull <image> manually first. |
did not surface a public IP (cloud) |
VM provisioned but networking is delayed. | Often resolves on retry; if persistent, check the AMI's first-boot time. |
verification timeout |
Bootstrap succeeded but the runner didn't heartbeat. | SSH/RDP into the target and inspect the runner process (systemd journal, sc.exe queryex). |
What NOT to do
- Don't put the runner token in audit-log search results. The
token grants control-plane writes; if it leaks, rotate via
signalman_api_key_revoke+ remint. - Don't reuse worker_name across hosts. Heartbeat upserts by
(org, name); two hosts heartbeating under the same name will fight for the row. - Don't deploy with
service_manager: noneand expect heartbeats. The binary is installed but not running; verification will time out by definition. - Don't run cloud transport without an explicit
ttl_minutesif the operator wants a long-lived runner — the cost-reaper default (60min) will terminate it. - Don't bake credentials into the binary URL. Use a
@signalman/registryblob URL (auth handled by the registry's bearer-token layer) instead of a presigned URL with embedded creds.
Follow-up suggestions
- After a successful deploy:
signalman_runner_listto see the new row + itslast_seen_at. Pair withsignalman_release_build --remoteto send work to it. - For cloud transport: capture the returned
bootstrap.detail.instance_idso the operator can terminate explicitly later (instead of waiting for the reaper). - For script transport: pipe the returned script body into a file the operator commits to their bootstrap repo. Idempotent re-runs resurrect the runner row in the control plane.
Source: ambray/signalman — distributed by TomeVault.