Skip to content

Tutorial — Provision a Web Server

This tutorial walks through a complete automation scenario: provisioning a web server with nginx, a config file, and a firewall rule.

  • SysopKit installed in a project or globally
  • Access to a target host via SSH

Every SysopKit script starts with main(), which creates a root execution context, runs your workflow, and sets process.exitCode on failure:

import { main } from 'sysopkit/start';
await main(async () => {
// Your automation logic here
});

Define the target host with an SSH connector.

import { SSHConnector } from 'sysopkit/connector/ssh';
import { main } from 'sysopkit/start';
await main(async () => {
await using c = new SSHConnector({ host: '192.168.1.10', user: 'sysop' });
});

The await using pattern ensures your SSH connection is cleaned up when the script exits.

Use apply() to run operations against one or more hosts:

import { apply } from 'sysopkit';
import { SSHConnector } from 'sysopkit/connector/ssh';
import { sh } from 'sysopkit/op/sh';
import { main } from 'sysopkit/start';
await main(async () => {
await using c = new SSHConnector({ host: '192.168.1.10', user: 'sysop' });
await apply('provision web', c, async () => {
await sh('hostname');
});
});

Step 4: Run Commands with Privilege Escalation

Section titled “Step 4: Run Commands with Privilege Escalation”

Many system operations require root. Use sudo() middleware to elevate privileges:

import { sudo } from 'sysopkit/middleware/sudo';
await apply('provision web', c, async () => {
await sudo(async () => {
await sh('apt update');
await sh('apt install -y nginx');
});
});

Use file operations to create configuration files idempotently:

import { createFile } from 'sysopkit/op/file';
await sudo(async () => {
await createFile({
path: '/etc/nginx/sites-available/default',
content: `server {
listen 80 default_server;
root /var/www/html;
index index.html;
}`,
mode: 0o644,
user: 'root',
group: 'root',
});
});

Use onChange() to react to config changes:

import { onChange, latch } from 'sysopkit';
const configChanged = latch();
await onChange(configChanged, async () => {
await createFile({
path: '/etc/nginx/sites-available/default',
content: NGINX_CONFIG,
mode: 0o644,
user: 'root',
group: 'root',
});
});
if (configChanged()) {
await sh('systemctl reload nginx');
}

Use task() to group related operations under a named context:

import { task } from 'sysopkit';
await sudo(async () => {
await task('install packages', async () => {
await sh('apt install -y nginx ufw');
});
await task('configure firewall', async () => {
await sh('ufw allow 80/tcp');
await sh('ufw allow 443/tcp');
});
await task('enable services', async () => {
await sh('systemctl enable --now nginx');
await sh('systemctl enable --now ufw');
});
});
import { apply, task, onChange, latch } from 'sysopkit';
import { SSHConnector } from 'sysopkit/connector/ssh';
import { sudo } from 'sysopkit/middleware/sudo';
import { createFile } from 'sysopkit/op/file';
import { sh } from 'sysopkit/op/sh';
import { main } from 'sysopkit/start';
const NGINX_CONFIG = `server {
listen 80 default_server;
root /var/www/html;
index index.html;
}`;
await main(async () => {
await using c = new SSHConnector({ host: '192.168.1.10', user: 'sysop' });
await apply('provision web', c, async () => {
await sudo(async () => {
await task('install packages', async () => {
await sh('apt update');
await sh('apt install -y nginx ufw');
});
const configChanged = latch();
await onChange(configChanged, async () => {
await createFile({
path: '/etc/nginx/sites-available/default',
content: NGINX_CONFIG,
mode: 0o644,
user: 'root',
group: 'root',
});
});
if (configChanged()) {
await task('reload nginx', async () => {
await sh('systemctl reload nginx');
});
}
await task('configure firewall', async () => {
await sh('ufw --force enable');
await sh('ufw allow 80/tcp');
await sh('ufw allow 22/tcp');
});
});
});
});

You now have a complete provisioning script: it connects over SSH, installs packages, manages config idempotently, reloads nginx only when the config changes, and configures the firewall. From here, continue with Core Concepts to understand the execution model, or scale out with Inventory to run this script against a fleet of hosts.