Execute custom scripts via HomeKit / Apple Home using Homebridge.
Core of the code written by @xxcombat. Original plugin: homebridge-script.
Use platform mode with:
on_off_switchesfor normal ON/OFF devicesstateless_switchesfor trigger-style devices
Legacy formats are still supported:
- platform
devicesarray- accessory-mode
accessoriesentriesSee LEGACY.md for legacy field details, examples, and migration guidance.
- In Homebridge UI, go to Plugins → homebridge-script2 → Plugin Config.
- Use the On/Off Switches and Stateless Switches sections.
- Save and restart Homebridge when prompted.
| Name | Value | Required | Notes |
|---|---|---|---|
on_off_switches |
array | no | Main section for standard ON/OFF switches |
stateless_switches |
array | no | Main section for one-shot trigger switches |
devices |
array | no (legacy only) | Legacy compatibility list (see LEGACY.md) |
| Name | Value | Required | Notes |
|---|---|---|---|
name |
(custom) | yes | Accessory name shown in Home app |
on |
(custom) | yes | Script/command to execute the ON action |
off |
(custom) | yes | Script/command to execute the OFF action |
fileState |
(custom) | fileState or state | File flag used as current state; if set, it overrides state |
state |
(custom) | fileState or state | Script to determine current ON/OFF state |
on_value |
(custom) | no (default "true") |
Value matched against normalized state output |
polling |
true/false |
no (default false) |
Enables periodic polling for state mode |
polling_interval |
integer ms | no (default 5000) |
Poll interval when polling is enabled |
polling_on_start |
true/false |
no (default true) |
Immediately runs state poll on startup |
state_cache_ttl_ms |
integer ms | no (default 1000) |
Cache TTL for burst reads |
reset_state_cache_on_set |
true/false |
no (default false) |
Resets/seeds state cache after successful manual set |
fail_on_state_exit_code |
true/false |
no (default false) |
Treat non-zero state exit code as read error |
unique_serial |
(custom) | no | Unique serial per accessory is recommended |
| Name | Value | Required | Notes |
|---|---|---|---|
name |
(custom) | yes | Accessory name shown in Home app |
trigger |
(custom) | yes | Script/command to execute trigger action |
auto_reset_ms |
integer ms | no | Delay before Home tile auto-resets |
stateless_trigger_on |
on/off |
no (default on) |
on triggers on ON; off triggers on OFF (tile defaults to ON) |
unique_serial |
(custom) | no | Unique serial per accessory is recommended |
- The
statescript output is normalized to lowercase and compared againston_value(default"true"). - on_value should be set to a string and use quotes. Default value is
"true". - If both
fileStateandstateare configured,fileStatetakes precedence: the state script is not used for status changes and the configured file flag is used instead. - If using fileState your on and off scripts should create the fileState file and delete the fileState file for homekit to see the changes.
- If a script returns a non-zero exit code but still prints a valid value to stdout (for example
trueorfalse), the plugin will use stdout to determine state. You can set fail_on_state_exit_code to true to treat non-zerostateexit code as read error. - When
pollingis enabled, thestatescript is executed on the configured interval and updates HomeKit if the value changes. - Polling options are ignored when
fileStateis configured, sincefileStatealready uses filesystem change notifications to dynamically update homekit status. - When
state_cache_ttl_msis greater than0,statereads are cached briefly to prevent duplicate script executions from burstgetrequests. - By default, manual HomeKit ON/OFF actions do not reset or extend
state_cache_ttl_ms. Setreset_state_cache_on_settotrueif you want successful manual set actions to reset the TTL timer and seed the cache with the newly set state. - If multiple
getrequests arrive while a state command is already running, they are coalesced and share the same in-flight command result. - Each
getStaterequest writes a single result log entry in the formatGetState <name>: ON/OFF (path: <homekit-get|polling>, source: <state-script|ttl-cache|in-flight-coalesced|file-state>). Where Path is telling you if this was the result of a polling request or a homekit initiated get request (out of the plugin's control). And source is where the value was sourced from, state-script execution result, ttl cache, in-flight coalesced, or from file-state. - The TTL cache is per-accessory instance (per configured outlet/switch), not global across all accessories.
- At startup with
polling_on_start: true, the first read for each accessory is a cache miss by design, so one state-script execution per accessory is expected before subsequent reads are served from TTL.
"platforms": [
{
"platform": "Script2Platform",
"name": "Script2",
"on_off_switches": [
{
"name": "Outlet 1",
"on": "/opt/scripts/on.sh 1",
"off": "/opt/scripts/off.sh 1",
"state": "/opt/scripts/state.sh 1",
"on_value": "true"
},
{
"name": "Outlet 2",
"on": "/opt/scripts/on.sh 2",
"off": "/opt/scripts/off.sh 2",
"fileState": "/opt/scripts/outlet2.flag",
"polling": false
}
],
"stateless_switches": [
{
"name": "Outlet 1 Reboot",
"trigger": "/opt/scripts/reboot.sh 1",
"auto_reset_ms": 500,
"stateless_trigger_on": "off"
},
{
"name": "Outlet 2 Reboot",
"trigger": "/opt/scripts/reboot.sh 2",
"auto_reset_ms": 700,
"stateless_trigger_on": "on"
}
]
}
](Requires Node.js >=20.19.0)
- Install homebridge using:
npm install -g homebridge - Install this plugin using:
npm install -g homebridge-script2 - Update your configuration file.
- Ensure scripts are executable and accessible by the Homebridge service user.
Homebridge runs scripts as the Homebridge service user, not your normal shell user.
A script that works as pi, ubuntu, or root may fail as homebridge.
Test your script as the same user that runs Homebridge:
sudo -u homebridge /absolute/path/to/script.shIf your Homebridge service runs as another user, replace homebridge with that user.
systemctl cat homebridge | grep -i '^User='If no User= is set, check your service/unit setup and logs to determine runtime context.
Most commonly:
- Wrong permissions (script or directories not executable/readable by Homebridge user)
- Wrong working directory
- Missing PATH in service environment
- Script exits early due to shell/line-ending issues
Yes, strongly recommended.
Do not rely on relative paths, ~, or shell-specific startup files.
Use absolute paths for:
- Script files
- Referenced files/directories
- Binaries/interpreters (
/usr/bin/python3,/usr/bin/node, etc.)
Example:
{
"on": "/home/homebridge/scripts/light_on.sh",
"off": "/home/homebridge/scripts/light_off.sh"
}Inside scripts:
#!/usr/bin/env bash
set -euo pipefail
cd /home/homebridge/scripts || exit 1
/usr/bin/python3 /home/homebridge/scripts/device_on.pychmod +x /home/homebridge/scripts/light_on.sh
chown homebridge:homebridge /home/homebridge/scripts/light_on.shAlso make sure the Homebridge user can traverse parent directories (x permission on each directory).
Check with:
namei -l /home/homebridge/scripts/light_on.shUse the exact command from your config as the Homebridge user:
sudo -u homebridge /home/homebridge/scripts/light_on.shIf this fails, Homebridge will fail too.
Add logging in your script so errors are visible:
#!/usr/bin/env bash
set -euo pipefail
exec >>/tmp/homebridge-script2.log 2>&1
echo "[$(date)] Starting light_on.sh as $(whoami) in $(pwd)"
/usr/bin/python3 /home/homebridge/scripts/device_on.py
echo "[$(date)] Done"Then inspect:
tail -n 100 /tmp/homebridge-script2.logYes. Scripts edited on Windows may have CRLF line endings and fail on Linux.
Convert to LF:
dos2unix /home/homebridge/scripts/light_on.shThis usually means:
- Status-check command/path is valid
- Action scripts (
on/off) have permission/path/runtime issues
Validate each action script independently as Homebridge user:
sudo -u homebridge /home/homebridge/scripts/light_on.sh
sudo -u homebridge /home/homebridge/scripts/light_off.sh- File path is absolute
- Homebridge user can create/delete/read that file
- Parent directory permissions are correct
- No conflicting process recreates/deletes file unexpectedly
- Always test as Homebridge user before troubleshooting plugin behavior.
- Always use absolute paths in config and scripts.
- Add logging and fail-fast flags (
set -euo pipefail) in shell scripts. - Keep scripts minimal; move complex logic to separate files you can test independently.
- Restart Homebridge after major script/permission changes to ensure a clean environment.