Skip to content

Connectors

Connectors abstract how commands reach a target system. Every connector implements connect(), isReady(), spawn(), and [Symbol.asyncDispose]().

interface Connector extends AsyncDisposable {
readonly host: string;
readonly name: string;
readonly vars: Record<string | symbol, any> | undefined;
readonly rsh: string[]; // command prefix for remote execution
connect(signal?: AbortSignal): Promise<void>;
isReady(signal?: AbortSignal): Promise<boolean>; // side-effect-free probe for waitForReady()
spawn(cmd: string[], signal?: AbortSignal): Promise<Process>;
[Symbol.asyncDispose](): Promise<void>;
}

The Process object returned by spawn() provides Web Streams for its input and output:

interface Process {
stdin: WritableStream;
stdout: ReadableStream;
stderr: ReadableStream;
exited: Promise<number>;
kill(code?: number): void;
}

Runs commands directly on the local host.

import { start, apply } from 'sysopkit';
import { LocalConnector } from 'sysopkit/connector/local';
await start(async () => {
await using c = new LocalConnector();
await apply('example', c, async () => {
await sh('echo hello');
});
});

Connects to remote hosts via native OpenSSH with ControlMaster multiplexing for connection reuse. Supports password and key authentication.

import { SSHConnector } from 'sysopkit/connector/ssh';
await using c = new SSHConnector({
host: '192.168.1.1',
user: 'sysop',
key: '/path/to/id_rsa',
strictHostKeyChecking: false,
});

The SSH connector builds a ControlMaster socket for multiplexing, validates the connection on connect(), and cleans up the control socket on dispose.

disableStrictHostKeyChecking() permanently stops verifying host keys on subsequently established connections — same flag set as constructing with strictHostKeyChecking: false (StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null). One-way: no re-enable on the same instance; re-pinning is a new connector’s job. A live multiplex master is untouched until it dies. Log the downgrade via the current context (ctx.warn(...)) so op logs show exactly when verification stopped.

import { SSHConnector } from 'sysopkit/connector/ssh';
await using c = new SSHConnector({ host: '192.168.1.1', user: 'sysop' });
// strict phase...
c.disableStrictHostKeyChecking();
// lax phase: survives key rotation across reboots

updateConnectorOptions(fn) unwraps any middleware chain and invokes fn with the base connector, so callers narrow with instanceof. Only operates on the ambient context’s connector; for single-connector flows (not parallel apply() batches).

import { updateConnectorOptions } from 'sysopkit';
import { SSHConnector } from 'sysopkit/connector/ssh';
updateConnectorOptions((conn) => {
if (conn instanceof SSHConnector) {
conn.disableStrictHostKeyChecking();
} else {
throw new Error(`refusing: unsupported connector '${conn.name}'`);
}
});

waitForReady(conn) polls the side-effect-free isReady() probe until the target accepts connections — useful for boot/wait flows. It takes a live connector instance:

import { waitForReady } from 'sysopkit';
import { SSHConnector } from 'sysopkit/connector/ssh';
await waitForReady(new SSHConnector({ host: '192.168.1.1' }));
// opts: { timeoutMs = 5*60_000, intervalMs = 5_000, signal }

Executes commands inside running Podman containers via podman exec.

import { PodmanConnector } from 'sysopkit/connector/podman';
await using c = new PodmanConnector({ host: 'my-app' });
await c.connect(); // validates container is running

When using resolveInventory(), host strings with prefixes automatically select the right connector:

  • ssh:hostname — SSH connector (default)
  • pod:container — Podman connector

Factories for other transports can be registered under custom prefixes.