Diagnostic Scripts
This tutorial covers SOVD diagnostic scripts - uploading, managing, and executing scripts on entities through the gateway REST API.
Overview
Diagnostic scripts (SOVD ISO 17978-3, Section 7.15) let you run diagnostic routines on individual entities. A script is a file - shell or Python - that the gateway executes as a subprocess when triggered via the REST API. The gateway tracks each execution’s lifecycle and exposes stdout, stderr, and exit status through a polling endpoint.
Scripts are available on Components and Apps entity types.
Typical use cases:
Run a sensor self-test on a specific component
Collect extended diagnostics that go beyond the standard data endpoints
Execute a calibration routine on a hardware driver
Trigger a cleanup or recovery procedure on a misbehaving node
The feature is disabled by default. Set scripts.scripts_dir to a
directory path to enable it. When disabled, all script endpoints return
HTTP 501.
Quick Example
Enable scripts in your gateway configuration:
ros2_medkit_gateway: ros__parameters: scripts: scripts_dir: "/var/ros2_medkit/scripts"
Upload a script via
multipart/form-data:curl -X POST http://localhost:8080/api/v1/components/main-computer/scripts \ -F "file=@check_disk.sh" \ -F 'metadata={"name": "Disk Check", "description": "Check disk usage and health"}'
Response (
201 Created):{"id": "script_1717123456_0", "name": "Disk Check"}
Execute the script:
curl -X POST http://localhost:8080/api/v1/components/main-computer/scripts/script_1717123456_0/executions \ -H "Content-Type: application/json" \ -d '{"execution_type": "now"}'
Response (
202 Accepted):{ "id": "exec_1717123500_0", "status": "running", "progress": null, "started_at": "2026-01-15T10:25:00Z", "completed_at": null, "parameters": null, "error": null }
Poll for completion:
curl http://localhost:8080/api/v1/components/main-computer/scripts/script_1717123456_0/executions/exec_1717123500_0Response when finished:
{ "id": "exec_1717123500_0", "status": "completed", "progress": null, "started_at": "2026-01-15T10:25:00Z", "completed_at": "2026-01-15T10:25:03Z", "parameters": { "stdout": "Filesystem Size Used Avail Use%\n/dev/sda1 50G 32G 18G 64%\n", "stderr": "", "exit_code": 0 }, "error": null }
Clean up the execution record and script when done:
# Delete the execution record curl -X DELETE http://localhost:8080/api/v1/components/main-computer/scripts/script_1717123456_0/executions/exec_1717123500_0 # Delete the uploaded script curl -X DELETE http://localhost:8080/api/v1/components/main-computer/scripts/script_1717123456_0
Script Formats
The gateway detects the script format from the uploaded filename extension and selects the appropriate interpreter:
Extension |
Interpreter |
Notes |
|---|---|---|
|
|
POSIX shell. Safest choice for maximum portability. |
|
|
Bash-specific features (arrays, |
|
|
Python 3. Useful for structured output or complex logic. |
Scripts are executed as subprocesses with their own process group. The gateway captures both stdout and stderr and includes them in the execution result along with the exit code.
Manifest-Defined Scripts
In addition to uploading scripts at runtime, you can pre-deploy scripts by
populating the ScriptsConfig.entries vector programmatically. This is
the approach used by ScriptProvider plugins - they can register a fixed
set of managed scripts that are always available, cannot be deleted through
the API, and appear with "managed": true in listing responses.
Each manifest entry supports these fields:
Field |
Type |
Description |
|---|---|---|
|
string |
Unique script identifier |
|
string |
Human-readable display name |
|
string |
What the script does |
|
string |
Absolute filesystem path to the script file |
|
string |
Interpreter selection: |
|
int |
Per-script timeout override (default: 300) |
|
[string] |
Glob patterns restricting which entities see this script (e.g., |
|
map |
Extra environment variables passed to the subprocess |
|
array |
Argument definitions (name, type, flag) for parameterized execution |
|
JSON |
JSON Schema describing accepted input parameters |
Managed scripts are validated at startup - the gateway logs a warning if a
script’s path does not point to an existing regular file.
Example: plugin providing a managed script
A ScriptProvider plugin can return pre-defined scripts in its list_scripts
implementation. See Plugin System for the full plugin tutorial.
Execution Lifecycle
Every script execution transitions through a defined set of states:
POST .../executions {"execution_type": "now"}
|
v
+----------+
| prepared | (initial, before subprocess starts)
+----------+
|
v
+---------+
| running | (subprocess active)
+---------+
|
+----------------------------+----------------------------+
| | |
v v v
+-----------+ +--------+ +------------+
| completed | | failed | | terminated |
+-----------+ +--------+ +------------+
(exit code 0) (non-zero exit or (stopped by user
internal error) or timeout)
Starting an Execution
POST /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions
Required body field:
execution_type(string) - currently"now"is the supported type
Optional body fields:
parameters(object) - input parameters passed to the scriptproximity_response(string) - proof-of-proximity token for scripts that require physical access
The response is 202 Accepted with a Location header pointing to the
execution status URL.
Polling Status
GET /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}
Returns the current ExecutionInfo with:
status- one ofprepared,running,completed,failed,terminatedprogress- optional integer (0-100) if the script reports progressstarted_at/completed_at- ISO 8601 timestampsparameters- output data includingstdout,stderr, andexit_codeerror- error details if the execution failed
Controlling a Running Execution
PUT /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}
Send a control action to stop a running script:
{"action": "stop"}
Supported actions:
stop- sends SIGTERM to the subprocess, allowing graceful shutdownforced_termination- sends SIGKILL for immediate termination
Deleting an Execution Record
DELETE /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}
Removes a completed, failed, or terminated execution record. Returns 204 No
Content. Returns 409 if the execution is still running.
Note
The gateway automatically evicts the oldest completed execution records
when the count exceeds scripts.max_execution_history (default: 100).
Timeouts
Each execution is subject to a timeout. When the timeout expires, the gateway
terminates the subprocess and sets the execution status to terminated.
Manifest-defined scripts can override the timeout per-script via
timeout_sec. Uploaded scripts use the global
scripts.default_timeout_sec (default: 300 seconds).
Concurrency
The gateway enforces a maximum number of concurrent executions across all
entities and scripts. If the limit is reached, new execution requests return
HTTP 429 with error code x-medkit-script-concurrency-limit. The default
limit is 5 (configurable via scripts.max_concurrent_executions).
Security
Disabling Uploads
For hardened deployments that should only run pre-deployed scripts, disable runtime uploads:
ros2_medkit_gateway:
ros__parameters:
scripts:
scripts_dir: "/var/ros2_medkit/scripts"
allow_uploads: false
When allow_uploads is false, POST .../scripts returns HTTP 400.
Pre-deployed manifest-defined scripts remain available for listing and
execution.
RBAC Roles
When Configuring Authentication is enabled (auth.enabled: true),
script endpoints are protected by role-based access control:
Role |
Script Permissions |
|---|---|
|
Read-only: list scripts, get script details, get execution status |
|
Viewer permissions plus: start executions, control (terminate) executions, delete execution records |
|
Operator permissions plus: upload scripts, delete scripts |
|
All permissions (inherits from configurator) |
This means:
A viewer can inspect what scripts are available and check execution results, but cannot run or modify anything.
An operator can run diagnostic scripts and stop them, but cannot upload new scripts or delete existing ones.
A configurator (or admin) has full control over the script library.
File Size Limits
Uploaded scripts are limited to scripts.max_file_size_mb (default: 10 MB).
Uploads exceeding the limit return HTTP 413.
ScriptProvider Plugins
The built-in DefaultScriptProvider stores uploaded scripts on the
filesystem and executes them as POSIX subprocesses. For alternative backends -
such as storing scripts in a database, fetching them from a remote service, or
running them in a sandboxed container runtime - you can implement a
ScriptProvider plugin.
A plugin ScriptProvider replaces the built-in backend entirely. It must
implement all 8 interface methods (list, get, upload, delete, start execution,
get execution, control execution, delete execution). The ScriptManager
wraps all calls with null-safety and exception isolation, so a plugin crash
will not take down the gateway.
See Plugin System for the full plugin development tutorial, including a ScriptProvider skeleton.
Configuration Reference
All scripts configuration parameters are documented in the server configuration reference:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
|
Directory for storing uploaded scripts. Empty string disables the feature. |
|
bool |
|
Allow uploading scripts via HTTP. |
|
int |
|
Maximum uploaded script file size in megabytes. |
|
int |
|
Maximum number of scripts executing concurrently. |
|
int |
|
Default timeout per execution in seconds (5 minutes). |
|
int |
|
Maximum completed executions to keep in memory. |
For the full server configuration reference, see Server Configuration.