{"id":13583663,"url":"https://github.com/pponce/homebridge-script2","last_synced_at":"2026-05-24T01:03:15.065Z","repository":{"id":19089517,"uuid":"77564651","full_name":"pponce/homebridge-script2","owner":"pponce","description":"Execute custom scripts via HomeKit apps","archived":false,"fork":false,"pushed_at":"2026-05-23T19:37:16.000Z","size":5716,"stargazers_count":97,"open_issues_count":5,"forks_count":23,"subscribers_count":10,"default_branch":"master","last_synced_at":"2026-05-23T21:22:17.234Z","etag":null,"topics":["homebridge","homebridge-plugin","homebridge-script","homebridge-script2","homekit"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pponce.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2016-12-28T22:17:43.000Z","updated_at":"2026-05-15T02:36:03.000Z","dependencies_parsed_at":"2022-07-23T12:17:01.942Z","dependency_job_id":null,"html_url":"https://github.com/pponce/homebridge-script2","commit_stats":null,"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/pponce/homebridge-script2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pponce%2Fhomebridge-script2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pponce%2Fhomebridge-script2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pponce%2Fhomebridge-script2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pponce%2Fhomebridge-script2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pponce","download_url":"https://codeload.github.com/pponce/homebridge-script2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pponce%2Fhomebridge-script2/sbom","scorecard":{"id":742849,"data":{"date":"2025-08-11","repo":{"name":"github.com/pponce/homebridge-script2","commit":"904a73bda5d57b600ce3e2d1fb5fcdfde028cce8"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 3/25 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T17:56:43.988Z","repository_id":19089517,"created_at":"2025-08-22T17:56:43.988Z","updated_at":"2025-08-22T17:56:43.988Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33416314,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"ssl_error","status_checked_at":"2026-05-23T22:14:43.778Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["homebridge","homebridge-plugin","homebridge-script","homebridge-script2","homekit"],"created_at":"2024-08-01T15:03:40.889Z","updated_at":"2026-05-24T01:03:15.022Z","avatar_url":"https://github.com/pponce.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"homebridge-script2\n==================\n\nExecute custom scripts via homekit/Home app.\n\nCore of the code written by [@xxcombat](https://github.com/xxcombat/). Great plugin that has served me well.\nOriginal plugin [homebridge-script](https://github.com/xxcombat/homebridge-script).\n\n## Homebridge UI Configuration\n\nThis plugin now includes a **Homebridge Config UI X** schema (`config.schema.json`) so you can add and manage `Script2Platform` devices directly from the UI instead of editing raw JSON manually. See below about upgrading to platform mode. Only use the user friendly Plugin Config UI X option if using the dynamic platform mode.\n\n- In Homebridge UI, go to **Plugins → homebridge-script2 → Plugin Config**.\n- Use the **Devices** list to add each Script2 switch/accessory and fill in commands/paths.\n- Save and restart Homebridge when prompted.\n\n## Platform mode\n\nThis plugin runtime supports **both**:\n- Legacy accessory mode (backward compatible in manual JSON)\n- New dynamic platform mode (`Script2Platform`)\n\nHomebridge Config UI schema is **platform-only** (dynamic platform) for reliable rich editing via the UI.\n\n### Backward compatibility\n- Existing accessory-based setups remain supported and unchanged.\n- If you choose to move to platform mode, you should remove the existing setup completely and start fresh with a new `platforms` configuration.\n\n### Strong recommendation: use a Child Bridge\nBecause this plugin depends on external scripts and shell execution, it is highly recommended to run it in a dedicated **Child Bridge** for reliability and isolation.\n\n### Platform configuration parameters\nEach entry in `devices` supports the same options as accessory mode:\n\nName            | Value         | Required                                    | Notes\n--------------- | ------------- | ------------------------------------------- | -------------\n`name`          | _(custom)_    | yes                                         | Name of accessory that will appear in Home app and is required\n`on`            | _(custom)_    | yes                                         | Script/command to execute the on action\n`off`           | _(custom)_    | yes                                         | Script/command to execute the off action\n`fileState`     | _(custom)_    | fileState or state is required (see note)   | File used as current state flag\n`state`         | _(custom)_    | fileState or state is required (see note)   | Script to determine current state\n`on_value`      | _(custom)_    | no* (default set to `\"true\"`)             | Value matched against `state` command output\n`polling`       | `true/false`  | no (default `false`)                       | Enables periodic polling for `state` command mode only\n`polling_interval` | integer ms | no (default `5000`)                        | Poll interval in milliseconds when `polling` is enabled\n`polling_on_start` | `true/false` | no (default `true`)                     | Immediately run a state poll when Homebridge starts\n`state_cache_ttl_ms` | integer ms | no (default `1000`)                     | Cache TTL for `state` reads to avoid duplicate script executions on burst GET requests\n`reset_state_cache_on_set` | `true/false` | no (default `false`)               | When enabled, successful manual ON/OFF actions reset the state cache timer and seed it with the set state\n`fail_on_state_exit_code` | `true/false` | no (default `false`)               | When enabled, non-zero exit code from the `state` command is treated as an error for that read request\n`unique_serial` | _(custom)_    | no (default set to `\"Script2 Serial number\"`) | Unique serial per device is recommended\n\n### Platform configuration example\n\n```\n\"platforms\": [\n  {\n    \"platform\": \"Script2Platform\",\n    \"name\": \"Script2\",\n    \"devices\": [\n      {\n        \"name\": \"RPC3 Socket 1\",\n        \"on\": \"/var/homebridge/rpc3control/on.sh 1\",\n        \"off\": \"/var/homebridge/rpc3control/off.sh 1\",\n        \"state\": \"/var/homebridge/rpc3control/state.sh 1\",\n        \"fileState\": \"/var/homebridge/rpc3control/script1.flag\",\n        \"on_value\": \"true\",\n        \"polling\": false,\n        \"polling_interval\": 5000,\n        \"polling_on_start\": true,\n        \"state_cache_ttl_ms\": 1000,\n        \"reset_state_cache_on_set\": false,\n        \"fail_on_state_exit_code\": false,\n        \"unique_serial\": \"platform-1234567\"\n      }\n    ]\n  }\n]\n```\n\nType note: use JSON booleans for `polling` (for example `\"polling\": true`), not strings like `\"polling\": \"true\"`.\n\n\n### State script behavior\n- The `state` script output is normalized to lowercase and compared against `on_value` (default `true`).\n- If both `fileState` and `state` are configured, `fileState` takes precedence: the state script is not used for status changes and the configured file flag is used instead.\n- If using fileState your on and off scripts should create the fileState file and delete the fileState file for homekit to see the changes.\n- If a script returns a non-zero exit code but still prints a valid value to stdout (for example `true` or `false`), the plugin will use stdout to determine state and log a warning with exit/stderr details.\n- To keep this behavior as default, `fail_on_state_exit_code` is `false` by default. Set it to `true` to treat non-zero state script exit codes as errors.\n- When `fail_on_state_exit_code` is enabled and the state script exits non-zero, the get request returns an error for that read request. In Home app this may appear as a temporary \"No Response\" / unavailable read when HomeKit requests state.\n- `fileState` checks also return read errors if checking the configured path throws.\n- This plugin does not attach HomeKit `StatusFault` to `Switch` services, which avoids unsupported-characteristic warnings in Homebridge logs.\n- Script best practice:\n  - Print only the state token (for example `true` or `false`) to `stdout`.\n  - Print diagnostics/errors to `stderr`.\n  - Use non-zero exit codes to indicate failures; the plugin logs detailed diagnostics with device name, action (`state`/`on`/`off`), exit code, stdout, stderr, and error message.\n- When `polling` is enabled, the `state` script is executed on the configured interval and updates HomeKit if the value changes.\n- Polling options are ignored when `fileState` is configured, since `fileState` already uses filesystem change notifications to dynamically update homekit status.\n- When `state_cache_ttl_ms` is greater than `0`, `state` reads are cached briefly to prevent duplicate script executions from burst `get` requests.\n- By default, manual HomeKit ON/OFF actions do **not** reset or extend `state_cache_ttl_ms`. Set `reset_state_cache_on_set` to `true` if you want successful manual set actions to reset the TTL timer and seed the cache with the newly set state.\n- If multiple `get` requests arrive while a state command is already running, they are coalesced and share the same in-flight command result.\n- Each `getState` request writes a single result log entry in the format `GetState \u003cname\u003e: ON/OFF (path: \u003chomekit-get|polling\u003e, source: \u003cstate-script|ttl-cache|in-flight-coalesced|file-state\u003e)`.\n  - For normal successful reads, this message is logged at debug level.\n  - If a state script exits non-zero but stdout is still accepted for state (`fail_on_state_exit_code: false`), this message is logged at info level.\n  - Path indicates if the read came from a HomeKit get or polling; source indicates where the value came from.\n- The TTL cache is per-accessory instance (per configured outlet/switch), not global across all accessories.\n- 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.\n\n## Installation\n(Requires Node.js \u003e=20.19.0)\n\n1. Install homebridge using: `npm install -g homebridge`\n2. Install this plugin using: `npm install -g homebridge-script2`\n3. Update your configuration file. See examples below that show the plugin working by using filestate for current state check as well as an example using state.sh script for current state check.\n4. Make sure scripts have been made executable (chmod +x scriptname.sh) and also accessible by the homebridge user. \n\n\n## Legacy accessory mode (still supported)\n\nLegacy `accessories` mode remains supported for backward compatibility.\nFor new installs, platform mode is recommended and fully supported in Homebridge Config UI rich editing.\nLegacy accessory mode remains available as runtime backward compatibility for existing manual JSON configs.\nIf you migrate from legacy accessory mode to platform mode, remove the old accessory setup and start fresh with a new `platforms` configuration.\n\n### Legacy accessory configuration parameters\n\nName            | Value         | Required                                    | Notes\n--------------- | ------------- | ------------------------------------------- | -------------\n`accessory`     | \"Script2\"     | yes                                         | Must be set to \"Script2\" and is required\n`name`          | _(custom)_    | yes                                         | Name of accessory that will appear in homekit app and is required\n`on`            | _(custom)_    | yes                                         | Location of script to execute the on action and is required\n`off`           | _(custom)_    | yes                                         | Location of script to execute the off action and is required\n`fileState`     | _(custom)_    | fileState or state is required (see note)   | Location of file that flags on or off current state. If this is configured the plugin will use the existence of this file to determine the current on or off state. If file exists, accessory is determined to be on. If file does not exist, accessory is determined to be off. This is not required. But if set, it will override using the state script. fileState or state must be configured. Use full path when setting this it's value. Do not use \"~/\".\n`state`         | _(custom)_    | fileState or state is required (see note)   | Location of script to execute the current state check. It must output to stdout the current state. It is not required if fileState is being used instead. fileState or state must be configured.\n`on_value`      | _(custom)_    | no* (see note, default set to \"true\")       | Used in conjunction with the state script. If using the state script this is the value that will be used to match against the state script output. If this value matches the output, then the accessory will be determined to be on. Required if using state script.\n`unique_serial` | _(custom)_    | no (default set to \"Script2 Serial number\") | If you have more than one \"accessory\" configured, please set unique values for each accessory. Unique values per accessory required for the Eve app.\n\n### Legacy configuration example 1 (`accessories`), using `fileState` for current state check:\n\n```\n\"accessories\":\n[\n    {\n        \"accessory\": \"Script2\",\n        \"name\": \"RPC3 Socket 1\",\n        \"on\": \"/var/homebridge/rpc3control/on.sh 1\",\n        \"off\": \"/var/homebridge/rpc3control/off.sh 1\",\n        \"state\": \"/var/homebridge/rpc3control/state.sh 1\",\n        \"fileState\": \"/var/homebridge/rpc3control/script1.flag\",\n        \"on_value\": \"true\",\n        \"unique_serial\": \"1234567\"\n    }\n]\n```\n\n#### Notes\n##### Using the above configuration as an example:\n- The on.sh script executes when you turn on the accessory via a homekit app. (In this case we are the using existence of a file to determine on or off current state, so you must insure the on.sh script creates the configured fileState file.\n- The off.sh script executes when you turn off the accessory via a homekit app. ( In this case we are using existence of a file to determine on or off current state, insure the off.sh script deletes the configured fileState file.)\n- The state.sh script in this case would not execute as fileState parameter overrides its use.\n- The configured fileState file is used as a flag. When the homekit app checks for current state it checks for the existence of this file. If it exists, current state is on. If it does not exist, current state is off.\n- HomeKit status updates are dynamic when using `fileState`: state changes are reflected as soon as the configured file is created or deleted.\n- The on_value in this case is not being used as it is only used when the state script is used to check for current state.\n\n### Legacy configuration example 2 (`accessories`), executing `state.sh` for current state check:\n\n```\n\"accessories\":\n[\n    {\n        \"accessory\": \"Script2\",\n        \"name\": \"Alarm of bike\",\n        \"on\": \"~/on.sh\",\n        \"off\": \"~/off.sh\",\n        \"state\": \"~/state.sh\",\n        \"on_value\": \"true\",\n        \"unique_serial\": \"1234567\"\n    }\n]\n```\n\n#### Notes\n##### Using the above configuration as an example:\n- The on.sh script executes when you turn on the accessory via a homekit app. (In this case we are executing a state script to determine on or off current state.)\n- The off.sh script executes when you turn off the accessory via a homekit app. ( In this case we are executing a state script to determine on or off current state.)\n- The state.sh script in this case would be executed to check current state.  Insure that this script outputs to stdout the matching on value as configured by the on_value config parameter. If the on_value matches the on value output of this script then the accessory will be determined to be on.\n- The configured fileState file is not used in this example. Because it was not configured, the state script is being used.\n- The on_value in this case is used to match against the state script output. If the value matches the output of the state script, the accessory is determined to be on.\n\n## Troubleshooting FAQ\n\n### Why does my script work in terminal but not in Homebridge?\n\nHomebridge runs scripts as the **Homebridge service user**, not your normal shell user.\nA script that works as `pi`, `ubuntu`, or `root` may fail as `homebridge`.\n\nTest your script as the same user that runs Homebridge:\n\n```bash\nsudo -u homebridge /absolute/path/to/script.sh\n```\n\nIf your Homebridge service runs as another user, replace `homebridge` with that user.\n\n### How can I confirm which user Homebridge runs as?\n\nOn systemd installs:\n\n```bash\nsystemctl cat homebridge | grep -i '^User='\n```\n\nIf no `User=` is set, check your service/unit setup and logs to determine runtime context.\n\n### Why does Homebridge say it ran the script, but nothing happens?\n\nMost commonly:\n\n1. Wrong permissions (script or directories not executable/readable by Homebridge user)\n2. Wrong working directory\n3. Missing PATH in service environment\n4. Script exits early due to shell/line-ending issues\n\n### Do I need absolute paths?\n\n**Yes, strongly recommended.**\nDo not rely on relative paths, `~`, or shell-specific startup files.\n\nUse absolute paths for:\n- Script files\n- Referenced files/directories\n- Binaries/interpreters (`/usr/bin/python3`, `/usr/bin/node`, etc.)\n\nExample:\n\n```json\n{\n  \"on\": \"/home/homebridge/scripts/light_on.sh\",\n  \"off\": \"/home/homebridge/scripts/light_off.sh\"\n}\n```\n\nInside scripts:\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\ncd /home/homebridge/scripts || exit 1\n/usr/bin/python3 /home/homebridge/scripts/device_on.py\n```\n\n### How do I verify permissions quickly?\n\n```bash\nchmod +x /home/homebridge/scripts/light_on.sh\nchown homebridge:homebridge /home/homebridge/scripts/light_on.sh\n```\n\nAlso make sure the Homebridge user can traverse parent directories (`x` permission on each directory).\n\nCheck with:\n\n```bash\nnamei -l /home/homebridge/scripts/light_on.sh\n```\n\n### What is the best “same as Homebridge” test command?\n\nUse the exact command from your config as the Homebridge user:\n\n```bash\nsudo -u homebridge /home/homebridge/scripts/light_on.sh\n```\n\nIf this fails, Homebridge will fail too.\n\n### How can I collect script debug logs?\n\nAdd logging in your script so errors are visible:\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\nexec \u003e\u003e/tmp/homebridge-script2.log 2\u003e\u00261\n\necho \"[$(date)] Starting light_on.sh as $(whoami) in $(pwd)\"\n/usr/bin/python3 /home/homebridge/scripts/device_on.py\necho \"[$(date)] Done\"\n```\n\nThen inspect:\n\n```bash\ntail -n 100 /tmp/homebridge-script2.log\n```\n\n### Could line endings break my script?\n\nYes. Scripts edited on Windows may have CRLF line endings and fail on Linux.\n\nConvert to LF:\n\n```bash\ndos2unix /home/homebridge/scripts/light_on.sh\n```\n\n### My `state` works but `on`/`off` does not. Why?\n\nThis usually means:\n- Status-check command/path is valid\n- Action scripts (`on`/`off`) have permission/path/runtime issues\n\nValidate each action script independently as Homebridge user:\n\n```bash\nsudo -u homebridge /home/homebridge/scripts/light_on.sh\nsudo -u homebridge /home/homebridge/scripts/light_off.sh\n```\n\n### If I use `fileState`, what should I check?\n\n- File path is absolute\n- Homebridge user can create/delete/read that file\n- Parent directory permissions are correct\n- No conflicting process recreates/deletes file unexpectedly\n\n\n## Recommended best practices\n\n- Always test as Homebridge user before troubleshooting plugin behavior.\n- Always use absolute paths in config and scripts.\n- Add logging and fail-fast flags (`set -euo pipefail`) in shell scripts.\n- Keep scripts minimal; move complex logic to separate files you can test independently.\n- Restart Homebridge after major script/permission changes to ensure a clean environment.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpponce%2Fhomebridge-script2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpponce%2Fhomebridge-script2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpponce%2Fhomebridge-script2/lists"}