Overview
Actions are executable commands in Joystick that can run either on the remote device via SSH or locally on the Joystick server. They support dynamic parameter substitution and flexible targeting.
Action structure
Actions are defined in two parts:
Action definition - The base action template
Run configuration - Device-specific command mapping
Action definition
packages/core/src/types/db.types.ts
export type ActionsRecord = {
id : string ;
name : string ;
created ?: IsoDateString ;
updated ?: IsoDateString ;
};
Unique identifier for the action (e.g., “reboot”, “set-mode”, “capture-image”)
Run configuration
The run collection maps actions to specific devices with executable commands:
packages/core/src/types/db.types.ts
export type RunRecord < Tparameters = unknown > = {
id : string ;
action : RecordIdString ; // Reference to action
device : RecordIdString ; // Reference to device model
command : string ; // Command template with parameters
target : RunTargetOptions ; // "device" or "local"
parameters ?: null | Tparameters ; // JSON Schema for validation
};
ID of the action to execute
ID of the device model (not device instance)
Command template with parameter placeholders (e.g., reboot -t $seconds)
Execution target:
device - Run via SSH on the remote device
local - Run on the Joystick server
JSON Schema defining required/optional parameters and validation rules
Parameter templating
Action commands support dynamic parameter substitution using the $variable syntax.
Built-in parameters
These parameters are automatically available in all action commands:
Device parameters
Service endpoints
{
device : "device_id" , // Current device ID
userId : "user_id" , // Authenticated user ID
... device . information // All device information fields
}
Parameter resolution
The parseActionCommand function replaces placeholders with actual values:
packages/core/src/action.ts
export function parseActionCommand (
device : DeviceResponse ,
action : string ,
params ?: Record < string , unknown >,
auth ?: { userId : string }
) {
const defaultParameters = {
device: device . id ,
mediamtx: STREAM_API_URL ,
switcher: SWITCHER_API_URL ,
userId: auth ?. userId ,
... device . information ,
};
const command = Object . entries ({
... defaultParameters ,
... params ,
}). reduce (( acc , [ key , value ]) => {
if ( acc . includes ( `$ ${ key } ` )) {
return acc . replaceAll ( `$ ${ key } ` , String ( value ));
}
return acc ;
}, action );
return command ;
}
Parameter examples
User parameters
Device information
Service URLs
User context
Custom parameters passed in the API request: {
"command" : "reboot -t $delay" ,
"parameters" : {
"type" : "object" ,
"properties" : {
"delay" : { "type" : "number" }
},
"required" : [ "delay" ]
}
}
API call: curl -X POST http://localhost:8000/api/run/device_id/reboot \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"delay": 10}'
Result: reboot -t 10 Access device connection details: {
"command" : "ssh $user@$host 'systemctl restart service'" ,
"target" : "local"
}
With device information: {
"user" : "root" ,
"host" : "192.168.1.100"
}
Result: ssh [email protected] 'systemctl restart service' Access internal service endpoints: {
"command" : "curl -X POST $mediamtx/v3/config/paths/add/$device" ,
"target" : "local"
}
Result: curl -X POST http://host.docker.internal:9997/v3/config/paths/add/device_id Include authenticated user information: {
"command" : "logger 'Action executed by user $userId'" ,
"target" : "device"
}
Result: logger 'Action executed by user abc123'
Execution targets
Actions can run in two different contexts:
Device target
Commands execute via SSH on the remote device:
packages/joystick/src/index.ts
if ( run . target === RunTargetOptions . device ) {
output = await runCommandOnDevice ( device , command );
}
The SSH connection uses the active slot configuration:
export async function runCommandOnDevice (
device : DeviceResponse ,
command : string
) {
const { host } = getActiveDeviceConnection ( device . information );
const { user , password , port = 22 , key } = device . information ;
// SSH with key, password, or default authentication
const result = key
? await $ `ssh -i ${ keyFileName } -o StrictHostKeyChecking=no -p ${ port } ${ user } @ ${ host } ' ${ command } '` . text ()
: password
? await $ `sshpass -p ${ password } ssh -o StrictHostKeyChecking=no -p ${ port } ${ user } @ ${ host } ' ${ command } '` . text ()
: await $ `ssh -o StrictHostKeyChecking=no -p ${ port } ${ user } @ ${ host } ' ${ command } '` . text ();
return result ;
}
Device commands automatically use the active SIM slot connection. See Device Management for details.
Local target
Commands execute on the Joystick server:
packages/joystick/src/index.ts
if ( run . target === RunTargetOptions . local ) {
output = await $ ` ${ { raw: command } } ` . text ();
}
Local commands run with the permissions of the Joystick service. Use caution with privileged operations.
Parameter validation
The parameters field in the run configuration accepts a JSON Schema for validation:
{
"parameters" : {
"type" : "object" ,
"properties" : {
"mode" : {
"type" : "string" ,
"enum" : [ "standby" , "active" , "recording" ]
},
"quality" : {
"type" : "number" ,
"minimum" : 1 ,
"maximum" : 10
},
"enabled" : {
"type" : "boolean"
}
},
"required" : [ "mode" ]
}
}
Validation happens before execution:
packages/joystick/src/index.ts
if ( run . parameters ) {
if ( ! body ) throw new Error ( "Parameters are required for this action" );
if ( ! validate ( body , run . parameters )) {
throw new Error ( "Invalid parameters for this action" );
}
}
Executing actions
Actions are executed via the REST API:
POST /api/run/:device/:action
Authorization : Bearer YOUR_JWT_TOKEN
Content-Type : application/json
{
"param1" : "value1" ,
"param2" : "value2"
}
Execution flow
Authentication - Verify user has access to the device
Device lookup - Retrieve device configuration
Action lookup - Find action by name
Run mapping - Get command template for device model
Validation - Check parameters against JSON Schema
Parsing - Replace parameter placeholders
Execution - Run on device or locally based on target
Logging - Record execution details in action_logs
{
"success" : true ,
"output" : "Command output here"
}
{
"success" : false ,
"error" : "Device device_id not found"
}
HTTP Status: 500
Action examples
Mode switching
{
"action" : "set-mode" ,
"device" : "model_id" ,
"command" : "mode-switch.sh $mode" ,
"target" : "device" ,
"parameters" : {
"type" : "object" ,
"properties" : {
"mode" : {
"type" : "string" ,
"enum" : [ "standby" , "active" , "recording" ]
}
},
"required" : [ "mode" ]
}
}
Stream configuration
{
"action" : "configure-stream" ,
"device" : "model_id" ,
"command" : "curl -X POST $mediamtx/v3/config/paths/add/$device -d '{ \" source \" : \" $source \" }'" ,
"target" : "local" ,
"parameters" : {
"type" : "object" ,
"properties" : {
"source" : { "type" : "string" }
},
"required" : [ "source" ]
}
}
File transfer
{
"action" : "backup-logs" ,
"device" : "model_id" ,
"command" : "tar -czf /tmp/logs-$device.tar.gz /var/log && scp /tmp/logs-$device.tar.gz backup@$host:/backups/" ,
"target" : "device" ,
"parameters" : null
}
Action logging
All action executions are logged to the action_logs collection:
{
user : "user_id" ,
device : "device_id" ,
action : "action_id" ,
parameters : { mode : "active" },
result : {
success : true ,
output : "Mode changed successfully"
},
execution_time : 1234 ,
ip_address : "192.168.1.50" ,
user_agent : "Mozilla/5.0..."
}
Execution time is measured in milliseconds and includes network latency for remote device commands.
Devices Configure device connections and information
Authentication Secure action execution with auth