ssh

Runs commands and writes files on a remote host. Shells out to the system ssh binary with -o BatchMode=yes -o StrictHostKeyChecking=accept-new, so authentication is whatever your ~/.ssh/config and ssh-agent provide. Passwords are not supported — keys only.

The provider takes no configuration block.

Kinds

ssh_exec

Runs a command on a remote host. Stratum re-runs the command whenever any attribute changes (typically command).

resource "ssh_exec" "uptime" {
  host    = host.prod.addr
  command = "uptime"
}

resource "ssh_exec" "bootstrap" {
  host       = host.prod.addr
  command    = "mkdir -p /opt/stratum"
  on_destroy = "rm -rf /opt/stratum"
}
attrrequiredtypedefaultdescription
hostyesstringSSH target in user@host form. Passed verbatim to ssh.
commandyesstringShell command to run on the remote host.
envnomapnoneEach entry becomes export KEY=VALUE; prepended to command. Values are shell-quoted. A secret.<name>.value ref is supported here (state stores a redaction marker, same as docker_container.env). Keys are sorted for deterministic command output.
on_destroynostringnoneCommand to run when this resource is deleted from config.

Stored state adds stdout, stderr, and exit_code from the last run.

The effective remote command is export K1=V1; export K2=V2; ...; <command> — a single shell invocation, not a separate environment. The env map is the right place for short, sensitive values (a GitHub PAT, a deploy token) that flow into a one-shot command without landing in a file. For values that need to persist on the host, use system_secret_file.

If the remote command exits non-zero, apply fails with the captured stderr.

When the resource is removed from config and on_destroy is set, the command is executed; otherwise delete is a no-op (with a log line).

Drift detection: read always returns Observed::Unknown("ssh_exec has no readable identity on the host"). ssh_exec resources show up in unreadable counts on stratum plan --refresh and after every stratum apply -y. That's intentional — a fire-and-forget shell command has no canonical "current state" to read back.

ssh_file

Writes a file to a remote path. The file is re-uploaded whenever its content changes, detected via sha256 of the prior state. Deletion runs rm -f on the recorded path.

resource "ssh_file" "motd" {
  host    = host.prod.addr
  path    = "/etc/motd"
  content = "Managed by stratum.\n"
  mode    = "0644"
}
attrrequiredtypedefaultdescription
hostyesstringSSH target in user@host form.
pathyesstringAbsolute destination path on the remote host.
contentyesstringFile contents, written verbatim.
modenostring0644File mode, passed to install -m.

Upload uses install -m <mode> /dev/stdin <path> with the content streamed via stdin. Binary content is not officially supported (the value comes through the parser as a UTF-8 string).

Stored state: { host, path, mode, sha256 }. The hash is what stratum compares on the next plan to decide whether to re-upload.

For richer file management (owner, group, auto-created parent dirs, content_file = "..." inlining), use system_file instead.

Drift detection: read runs a single-roundtrip probe: if [ -e <path> ]; then printf '%s|' "$(stat -c %a <path>)"; sha256sum <path> | cut -d' ' -f1; else echo MISSING; fi. Returns Absent for missing files, Present { host, path, mode, sha256 } otherwise.

Apply behavior

Trace lines, one per resource:

[ssh] EXEC `uptime` on root@192.0.2.10
[ssh] FILE `motd` -> root@192.0.2.10:/etc/motd
[ssh] FILE `motd` on root@192.0.2.10 unchanged (sha256 match)