Sample code for 30+ languages & platforms
Zig

SSH Parallel Remote Commands on Multiple Servers

See more SSH Examples

Demonstrates running the same command on several servers simultaneously. It is simply a matter of using one Ssh object per server, starting the command on each with QuickCmdSend, and then polling each connection with QuickCmdCheck until all have finished.

Background: This is the fan-out pattern behind configuration-management and monitoring tools: query or update a whole fleet in roughly the time the slowest single host takes, rather than the sum of all of them. Each server needs its own object because each has its own connection and authentication state. Note that results come back in whatever order the servers finish, which is why the example tracks completion per connection. Production code would keep the objects in arrays instead of repeating the logic per server.

Chilkat Zig Downloads

Zig
const std = @import("std");
const chilkat = @import("chilkat");

pub fn main(init: std.process.Init) !void {
    const alloc = init.arena.allocator();

    // This example requires the Chilkat API to have been previously unlocked.
    // See Global Unlock Sample for sample code.

    // Demonstrates running the same command on several servers simultaneously.  It is simply a
    // matter of using one Ssh object per server.

    const ssh1 = try chilkat.Ssh.init();
    defer ssh1.deinit();
    const ssh2 = try chilkat.Ssh.init();
    defer ssh2.deinit();
    const ssh3 = try chilkat.Ssh.init();
    defer ssh3.deinit();

    const port = 22;

    // Normally you would not hard-code the password in source.  You should instead obtain it
    // from an interactive prompt, environment variable, or a secrets vault.
    const password = "mySshPassword";

    ssh1.connect("ssh-server1.example.com", port) catch {
        std.debug.print("{s}\n", .{try ssh1.getLastErrorText(alloc)});
        return;
    };

    ssh1.authenticatePw("mySshLogin", password) catch {
        std.debug.print("{s}\n", .{try ssh1.getLastErrorText(alloc)});
        return;
    };

    ssh2.connect("ssh-server2.example.com", port) catch {
        std.debug.print("{s}\n", .{try ssh2.getLastErrorText(alloc)});
        return;
    };

    ssh2.authenticatePw("mySshLogin", password) catch {
        std.debug.print("{s}\n", .{try ssh2.getLastErrorText(alloc)});
        return;
    };

    ssh3.connect("ssh-server3.example.com", port) catch {
        std.debug.print("{s}\n", .{try ssh3.getLastErrorText(alloc)});
        return;
    };

    ssh3.authenticatePw("mySshLogin", password) catch {
        std.debug.print("{s}\n", .{try ssh3.getLastErrorText(alloc)});
        return;
    };

    // This command sleeps for 5 seconds and then prints the system date/time, making the parallel
    // execution easy to observe.
    const cmd = "sleep 5; date";

    // Start the command on each server.  QuickCmdSend returns immediately.
    const channel1 = ssh1.quickCmdSend(cmd);
    if (channel1 < 0) {
        std.debug.print("{s}\n", .{try ssh1.getLastErrorText(alloc)});
        return;
    }

    const channel2 = ssh2.quickCmdSend(cmd);
    if (channel2 < 0) {
        std.debug.print("{s}\n", .{try ssh2.getLastErrorText(alloc)});
        return;
    }

    const channel3 = ssh3.quickCmdSend(cmd);
    if (channel3 < 0) {
        std.debug.print("{s}\n", .{try ssh3.getLastErrorText(alloc)});
        return;
    }

    // The command is now running on all three servers at once.  Poll each connection until every
    // command has finished.  (Production code would keep the objects in arrays rather than
    // repeating the logic.)
    const poll_timeout_ms = 50;
    var num_finished: i32 = 0;
    var finished1: bool = false;
    var finished2: bool = false;
    var finished3: bool = false;

    while (num_finished < 3) {
        if (!finished1) {
            const ch1 = ssh1.quickCmdCheck(poll_timeout_ms);
            if (ch1 == -2) {
                std.debug.print("{s}\n", .{try ssh1.getLastErrorText(alloc)});
                return;
            }

            if (ch1 >= 0) {
                std.debug.print("---- ssh1 channel {d} finished ----\n", .{ch1});
                const out1 = ssh1.getReceivedText(alloc, ch1, "utf-8") catch {
                    std.debug.print("{s}\n", .{try ssh1.getLastErrorText(alloc)});
                    return;
                };

                std.debug.print("{s}\n", .{out1});
                finished1 = true;
                num_finished = num_finished + 1;
            }
        }

        if (!finished2) {
            const ch2 = ssh2.quickCmdCheck(poll_timeout_ms);
            if (ch2 == -2) {
                std.debug.print("{s}\n", .{try ssh2.getLastErrorText(alloc)});
                return;
            }

            if (ch2 >= 0) {
                std.debug.print("---- ssh2 channel {d} finished ----\n", .{ch2});
                const out2 = ssh2.getReceivedText(alloc, ch2, "utf-8") catch {
                    std.debug.print("{s}\n", .{try ssh2.getLastErrorText(alloc)});
                    return;
                };

                std.debug.print("{s}\n", .{out2});
                finished2 = true;
                num_finished = num_finished + 1;
            }
        }

        if (!finished3) {
            const ch3 = ssh3.quickCmdCheck(poll_timeout_ms);
            if (ch3 == -2) {
                std.debug.print("{s}\n", .{try ssh3.getLastErrorText(alloc)});
                return;
            }

            if (ch3 >= 0) {
                std.debug.print("---- ssh3 channel {d} finished ----\n", .{ch3});
                const out3 = ssh3.getReceivedText(alloc, ch3, "utf-8") catch {
                    std.debug.print("{s}\n", .{try ssh3.getLastErrorText(alloc)});
                    return;
                };

                std.debug.print("{s}\n", .{out3});
                finished3 = true;
                num_finished = num_finished + 1;
            }
        }
    }

    ssh1.disconnect();
    ssh2.disconnect();
    ssh3.disconnect();
}