plugin-development
GitHub指导 NetAlertX 插件的开发、配置与运行。涵盖插件目录结构、配置文件规范、设置模式及数据契约,帮助开发者创建和调试插件功能。
Trigger Scenarios
Install
npx skills add netalertx/NetAlertX --skill plugin-development -g -y
SKILL.md
Frontmatter
{
"name": "plugin-development",
"description": "Create and run NetAlertX plugins. Use this when asked to create a plugin, run a plugin, test a plugin, or develop plugin functionality."
}
Plugin Development
Expected Workflow
- Read this skill and
docs/PLUGINS_DEV.mdfor full context. - Find or create the plugin in
server/plugins/<code_name>/. - Read the plugin's
config.jsonand script to understand its functionality and settings. - Run:
python3 server/plugins/<code_name>/script.py - Retrieve the result from
/tmp/log/plugins/last_result.<PREF>.logquickly — the backend processes and deletes it almost immediately.
Plugin Structure
server/plugins/<code_name>/
├── config.json # Manifest: settings, data contract, DB column mapping
├── script.py # Main script (or equivalent, depending on data_source)
└── README.md # Setup/usage docs
code_namemust match the folder name.unique_prefixdrives every setting key and filename (e.g.ARPSCAN→ARPSCAN_RUN,last_result.ARPSCAN.log). Uppercase letters only, no underscores/numbers, must be unique across all plugins.- Ensure
sys.pathincludes/app/server/pluginsand/app/server(as inserver/plugins/__template/rename_me.py).
Settings Pattern
<PREF>_RUN: execution phase (see below). Should default to"disabled"for any non-core plugin.<PREF>_RUN_SCHD: cron-like schedule — check a similar existing plugin for precedent (e.g.pihole_api_scanuses*/5 * * * *) rather than inventing a new cadence.<PREF>_CMD: script path.<PREF>_RUN_TIMEOUT: timeout in seconds — enforced by the core plugin runner as the whole script's kill-timeout (server/plugin.pypasses it straight tosubprocess(..., timeout=...)). Not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file. Two correct alternatives:config.json's"timeoutMultiplier": trueon aparams[]entry for a config-declared, known-length loop (seearp_scan);plugin_helper.per_item_timeout()for a runtime-variable-length loop (see the_publisher_*plugins).<PREF>_WATCH: columns to watch for changes.<PREF>_IMPORT_ON: optional — gates whether this run's rows get promoted intoCurrentScan(only relevant ifmapped_to_table: "CurrentScan"). Seedocs/PLUGINS_IMPORT_BEHAVIOR.mdfor the related per-rowscanCreatesDevice/scanNotificationMode/scanPresencecolumns.dataTypeanddefault_valuemust agree.dataType: "array"/"object"needs a real JSON literal fordefault_value('["default"]'), not a bare string ("default").setting_value_to_python_type()(server/helper.py)json.loads()s the default at runtime; a bare string fails silently — logged, and[]is returned instead of your default (e.g.devParentRelType,UI_theme,UI_TOPOLOGY_ORDER). IfelementOptionsalready setsmultiple/orderable: "false", the setting is scalar — usedataType: "string"instead.
Data Contract
from plugin_helper import Plugin_Objects
plugin_objects = Plugin_Objects(RESULT_FILE)
plugin_objects.add_object(...) # once per discovered item
plugin_objects.write_result_file() # exactly once, at the end
Full column spec: docs/PLUGINS_DEV_DATA_CONTRACT.md. Note helpVal1-4/watchedValue1-4 both preserve a real 0/False you pass explicitly — only an omitted (None) value defaults to "".
Every mapped field (objectPrimaryId/objectSecondaryId/watchedValue1-4/extra/helpVal1-4) is HTML/control-char-stripped by default before it's persisted: plugin output is untrusted (network responses, device-reported names, etc.). foreignKey is always sanitized too, unconditionally. Only opt a column out ("allow_raw_text": true) if it's a display-only type (textarea_readonly); see docs/PLUGINS_DEV.md#field-sanitization.
Execution Phases
| Phase | Trigger |
|---|---|
once |
Once at startup |
schedule |
On cron schedule |
always_after_scan |
After every scan |
before_name_updates |
Before name resolution |
on_new_device |
When new device detected |
on_notification |
When notification triggered |
Plugin Formats
| Format | Purpose | Phase |
|---|---|---|
| publisher | Send notifications | on_notification |
| dev scanner | Create/manage devices | schedule |
| name discovery | Discover device names | before_name_updates |
| importer | Import from services | schedule |
| system | Core functionality | schedule |
Before Opening a PR
Check the plugin against the Conventions Checklist — RUN default, schedule precedent, RUN_TIMEOUT semantics, reusing core settings instead of duplicating them, description length (renders in the Settings UI — keep it short), the multi-instance settings pattern (nested array + popup-form, see rest_import, not a hardcoded "primary"/"secondary" pair), allow_raw_text restricted to display-only column types, and scanSourcePlugin's static value matching unique_prefix exactly. Most plugin PR review comments trace back to one of these, and test/plugins/test_plugin_conventions.py mechanically enforces the RUN-default, description-length, hardcoded-default-drift, RUN_TIMEOUT-reuse-in-loop, array/object dataType-default_value-mismatch, allow_raw_text-type-restriction, and scanSourcePlugin-value-mismatch items — run it after touching a plugin.
If the plugin needs a new system package or Python dependency, mirroring it into the root Dockerfile/requirements.txt alone is not enough: see the Conventions Checklist's build-target-mirroring bullet for .devcontainer/Dockerfile (regenerate via .devcontainer/scripts/generate-configs.sh, don't hand-edit it), Dockerfile.debian, and install/ubuntu24/install/proxmox's own requirements.txt files.
Starting Point
Copy server/plugins/__template/ and customize. Read docs/PLUGINS_DEV.md for the full development guide.
Version History
- f6010e0 Current 2026-09-27 22:52
-
cd1d0ed
2026-09-22 11:06
更新插件设置模式说明,强调 dataType 与 default_value 一致性,修正超时处理逻辑,清理文档并修复配置问题。
- a686a01 2026-09-03 06:46


