Ready-made examples¶
Complete recipes, from zero to working. Each one is meant for you to copy and tune to your case: swap the host names, the service and the credentials, and it is ready. They all assume you know the basics of agents, runbooks and automations; if not, you can still follow along, the steps are self-contained.
| Recipe | Trigger | Type | Target |
|---|---|---|---|
| 1. Restart a Windows service | Alert | Ansible (WinRM) | Windows |
| 2. Restart a Linux service | Alert | Python (SSH) | Linux |
| 3. Scheduled disk cleanup | Schedule | Ansible | Linux |
| 4. Handle a ticket | Ticket | Python | any |
Recipe 1: restart a Windows service (from scratch)¶
The full case: you have a Windows server where the W3SVC service (IIS) sometimes goes down, and you want monitoring to restart it on its own. We go from the empty container to the automation firing.
Step 1: prepare the inventory on the agent host¶
The agent will talk to Windows over WinRM. The inventory and credentials live on the agent's machine, never on the platform. Create the folder and file:
mkdir -p /opt/sp1-agent/secrets
cat > /opt/sp1-agent/secrets/inventory.ini <<'EOF'
[windows]
win01 ansible_host=10.0.0.21
[windows:vars]
ansible_connection=winrm
ansible_user=Administrator
ansible_password=CHANGE_ME
ansible_winrm_transport=ntlm
ansible_port=5985
ansible_winrm_server_cert_validation=ignore
EOF
The password stays here
ansible_password stays in this file, on the agent's machine. The platform never receives it. In production, prefer a vault (Ansible Vault) or a dedicated service account with the least privilege.
Step 2: bring up the agent with the folder mounted¶
Generate the agent under Administration → Agents & Identities → Add agent (the token is shown once, copy it). Then start the container mounting the inventory folder into the work directory:
docker run -d --name sp1-agent \
-e SP1_URL=https://platform.specialone.io \
-e SP1_AGENT_ID=win-agent \
-e SP1_AGENT_SECRET=<token-shown-once> \
-e SP1_TENANT=<tenant-code> \
-v /opt/sp1-agent/secrets:/opt/sp1-agent/work \
registry.specialone.io/sp1-automation-agent:latest
The -v is what wires the folder: the inventory.ini you created becomes /opt/sp1-agent/work/inventory.ini inside the agent, and it finds it on its own. In seconds the agent shows up as Active in the list.
Step 3: create the runbook¶
Under DevOps → Runbooks → Add runbook, type Ansible, name restart-windows-service:
---
- hosts: windows
gather_facts: false
tasks:
- name: Restart the Windows service
ansible.windows.win_service:
name: "{{ service | default('W3SVC') }}"
state: restarted
register: r
- name: Confirm it is up
ansible.windows.win_service_info:
name: "{{ service | default('W3SVC') }}"
register: check
- name: Fail if not running
ansible.builtin.assert:
that: check.services[0].state == 'started'
fail_msg: "Service did not come back"
success_msg: "Service running after restart"
Notice the playbook already validates: it restarts, reads the state and fails if it did not come back. That way the execution only shows as success when the service is truly up.
Step 4: build the automation¶
Under DevOps → Automations → Add → Observability:
- Action: agent
win-agent, runbookrestart-windows-service. - Trigger: on alert. Host
win01, alert name*IIS*or*W3SVC*, Critical severity. - To target only the host that alerted, add the
limitparameter mapping the alert'shostnamefield (so--limitrestricts the run).
Step 5: validate¶
Before waiting for a real alert, use the Run now tab with service=W3SVC and watch the log live. The PLAY RECAP with failed=0 and the message "Service running after restart" confirm it. From then on, any alert matching the trigger restarts IIS on its own.
Recipe 2: restart a Linux service (on alert)¶
Same idea as Recipe 1, but on Linux and with Python + SSH, which is handy when you would rather not keep an Ansible inventory and prefer a direct script.
Step 1: SSH key on the agent¶
On the agent's machine, keep a key that reaches the target hosts (a user with sudo on the service). Mount the folder into the container like in Recipe 1 (-v /opt/sp1-agent/secrets:/opt/sp1-agent/work), with the key at /opt/sp1-agent/secrets/id_ed25519.
Step 2: Python runbook¶
DevOps → Runbooks → Add, type Python, name restart-service:
import os
import json
import subprocess
params = json.loads(os.environ.get("SP1_PARAMS") or "{}")
host = params.get("hostname") or params.get("host")
service = params.get("service", "nginx")
# validate the input: never trust raw alert data
if not host or not all(c.isalnum() or c in ".-" for c in host):
raise SystemExit(f"invalid host: {host!r}")
ssh = ["ssh", "-i", "/opt/sp1-agent/work/id_ed25519",
"-o", "StrictHostKeyChecking=accept-new",
f"deploy@{host}"]
# restart and confirm the service came back (is-active)
subprocess.run(ssh + ["sudo", "systemctl", "restart", service], check=True, timeout=60)
r = subprocess.run(ssh + ["systemctl", "is-active", service],
capture_output=True, text=True, timeout=30)
print(f"{service} on {host}: {r.stdout.strip()}")
raise SystemExit(0 if r.stdout.strip() == "active" else 1)
Step 3: automation¶
Observability, the Linux agent, runbook restart-service, trigger on alert in the web* group. The alert fields (hostname, alert_name, severity, etc.) arrive on their own in SP1_PARAMS, the script reads hostname directly.
Step 4: validate¶
Run now tab with host=web03, service=nginx. The log should end with nginx on web03: active and the execution as completed.
Recipe 3: scheduled disk cleanup¶
Not every automation comes from an alert. This one runs every night, with no problem trigger, just the clock.
Step 1: Ansible runbook¶
DevOps → Runbooks → Add, type Ansible, name disk-cleanup:
---
- hosts: all
gather_facts: false
tasks:
- name: Find temp files older than 7 days
ansible.builtin.find:
paths: /tmp
age: 7d
recurse: true
register: old
- name: Delete
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ old.files }}"
loop_control: { label: "{{ item.path }}" }
- name: Summary
ansible.builtin.debug:
msg: "{{ old.files | length }} file(s) removed"
Step 2: schedule it¶
Under DevOps → Job Scheduler → Add:
- Agent: the one for the target environment.
- Runbook:
disk-cleanup. - Frequency: Daily, at
03:00, Monday to Friday.
Each run enters the agent's queue at the scheduled time and shows up in Executions. The schedule is also listed in the agent's Triggers, alongside the alerts and tickets that fire it.
Centralizes what would be cron scattered around
Instead of a crontab per machine (that nobody audits), the routine lives in one place, with a history of each run and the log of what was deleted.
Recipe 4: handle a ticket (and reply on the ticket itself)¶
The most complete one: a user opens a ticket, the agent runs the action and writes the reply back on the ticket, resolving it on its own. No security risk, it is a common operational help-desk action (restart an application on request).
How it works¶
The trick is that the agent receives, along with the ticket data, a platform API token (SP1_API_TOKEN, Service Manager scope). With it, the runbook calls the Service Desk API and writes back on the ticket.
Step 1: runbook that acts and replies¶
DevOps → Runbooks → Add, type Python, name handle-restart:
import os
import json
import subprocess
import urllib.request
params = json.loads(os.environ.get("SP1_PARAMS") or "{}")
ticket_id = params.get("ticket_id") # ticket number (comes from the trigger)
host = params.get("host", "app02")
service = params.get("service", "app")
# 1) run the requested action
ok = subprocess.run(
["ssh", "-i", "/opt/sp1-agent/work/id_ed25519", f"deploy@{host}",
"sudo", "systemctl", "restart", service],
timeout=60,
).returncode == 0
# 2) reply ON THE TICKET ITSELF via the platform API
api = os.environ["SP1_URL"] + f"/api/v2/itsm/tickets/{ticket_id}"
body = {
"public_log": (f"Service {service} restarted successfully on {host}."
if ok else f"Failed to restart {service} on {host}."),
"status": "resolved" if ok else "assigned",
}
req = urllib.request.Request(
api, data=json.dumps(body).encode(), method="PATCH",
headers={"Authorization": "Bearer " + os.environ["SP1_API_TOKEN"],
"Content-Type": "application/json"},
)
urllib.request.urlopen(req, timeout=30)
print("ticket", ticket_id, "updated")
raise SystemExit(0 if ok else 1)
The public_log becomes a public comment on the ticket (the requester sees it), and status: resolved closes the request. If it fails, it leaves the ticket assigned for a human to look at, instead of resolving it wrong.
Step 2: give the agent the right scope¶
The agent needs to be able to write to the Service Desk. In Agents & Identities, on the agent's menu → Edit permissions, set Service Manager: Read + write. Without it, the API call is refused.
Step 3: ticket automation¶
Under DevOps → Automations → Add → Service Management → Ticket triggers agent:
- Scope: type Request, category "Service restart" (the category/subcategory you use).
- Agent and runbook: the agent and
handle-restart. - Parameter map: map the ticket number to the
ticket_idparameter (that is what the runbook uses to reply back).
Step 4: validate (safely)¶
Turn the automation on in dry-run mode first: it shows which tickets would match without running anything. Check the scope, then turn dry-run off. Open a test ticket in the category and watch: in seconds the agent replies on the ticket with the comment and the resolved status, and the execution shows up in Executions.
Pick risk-free actions
Prefer automating operational, reversible actions (restart a service, drain a queue, reload a cache, run a diagnostic and attach the result). Keep out of the automatic flow anything sensitive or irreversible (touching permissions, deleting data, changing passwords), that kind of request should still go through a human.
Next steps¶
-
Agent details
Inventory, WinRM, lifecycle and agent security.
-
Automation details
The tabs, the schedules, the executions and the report.