Repository navigation
Poll apcupsd UPSes with a native NIS client instead of apcaccess - #61
Merged
Merged
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
The collector replacement still needs validation against a real apcupsd daemon before its protocol compatibility can be confirmed.
2 open issues
What changed in this PR
Replaces the Cacti plugin’s apcaccess subprocess with native PHP network polling, removing poller-side binary requirements and enabling Windows support.
Changes:
- Adds a bounded NIS client and integrates polling, failure logging, and recovery handling.
- Includes #60’s translation-formatting fixes and regression tests.
- Updates documentation, translation messages, and coverage configuration.
| File | Description |
|---|---|
| upses.php | Fixes Graph Automation message formatting. |
| tests/Unit/SecurityHelpersTest.php | Removes obsolete shell-helper tests. |
| tests/Unit/NutWiringTest.php | Updates timeout and logging assertions. |
| tests/Unit/NisClientTest.php | Adds protocol and socket tests. |
| tests/Unit/I18nPlaceholderTest.php | Adds translation-formatting regression checks. |
| tests/Integration/SecurityWiringTest.php | Checks native polling integration. |
| tests/Integration/PollerCommandSecurityTest.php | Tests network-input validation. |
| tests/bootstrap-unit.php | Models Cacti translation formatting. |
| README.md | Documents native polling requirements. |
| poller_apcupsd.php | Uses NIS polling and clears recovered errors. |
| phpunit.xml | Includes NIS client in coverage. |
| locales/po/cacti.pot | Updates collector messages. |
| includes/nut.php | Fixes translated error formatting. |
| includes/nis.php | Implements the native NIS client. |
| includes/functions.php | Removes shell-command helpers. |
| includes/dashboard.php | Fixes translated metric captions. |
| CHANGELOG.md | Records features and fixes. |
Review effort: Balanced
Add includes/nis.php, a client for the apcupsd network information server (TCP 3551: a length-prefixed 'status' request, then length-prefixed KEY : value records ended by an empty one), and use it from collect_ups_data() in place of running apcaccess. Units are stripped from numeric values as apcaccess -u did. The per-UPS timeout is applied as it is for NUT: normalised to 1-120 seconds, bounding the connect and, at twice its length, the whole exchange. Remove the shell command helpers and the exec_with_timeout() call, log failed apcupsd queries to the Cacti log, clear a recovered UPS's stored error, and update the README, CHANGELOG, translation template and tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The changed-line coverage check failed because the new client was neither in the Clover report nor allowlisted; list it in phpunit.xml's coverage sources, as nut.php is. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…parate reads - README: NISIP is the interface apcupsd listens on (default 0.0.0.0, all interfaces; 127.0.0.1 means local only), not a list of allowed clients. Setting it to the poller's address would stop the daemon binding. Say so, and describe letting the poller in separately: a firewall rule for the NIS port, or TCP Wrappers (/etc/hosts.allow) when apcupsd was built with it. - NisClientTest: the pieces test wrote the whole reply to a socket pair before reading, and stream sockets do not keep write boundaries, so the client could read it in one fread() and the test passed without accumulating short reads. Feed the client from a stream that returns exactly the pieces it is given (one byte at a time; split inside a length and a record; with empty reads between). Checked against a client that discards earlier partial reads: the old test still passed, the new ones fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TheWitness
force-pushed
the
feature/native-apcupsd-nis
branch
from
October 19, 2026 13:16
6cf15a6 to
df842c9
Compare
|
xmacan
approved these changes
Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Description
Polls
apcupsdUPS' with a native PHP client for the apcupsd network protocol instead of runningapcaccess, the same approach the NUT collector uses.apcupsdandapcaccessno longer have to be installed on the Cacti poller, andapcupsdUPS' can be polled from Windows pollers (the old code only looked forapcaccessin/usr/sbin,/usr/binand/usr/local/bin).This branch was stacked on #60, which has since merged; it is rebased onto develop and now contains only this change.
How it works
includes/nis.phpspeaks the apcupsd Network Information Server (NIS) protocol: it sends the textstatuswith a 2-byte big-endian length, then reads length-prefixedKEY : valuerecords until the empty record that ends the reply. This is whatapcaccess -u -h host:portdoes.apcaccess -udid (121.0 Voltsbecomes121.0). Only a number followed by a known unit word is changed, so text and dates pass through untouched.collect_ups_data()inpoller_apcupsd.phpnow callsapcupsd_nis_query()and keeps its existing field mapping and host up/down handling, using the existingset_ups_host_state().apcupsd_build_apcaccess_command(),apcupsd_escape_shellcmd(),apcupsd_escape_shellarg()) and theexec_with_timeout()call are gone, so the apcupsd collector no longer executes anything.Behaviour changes to be aware of
WARNING: apcupsd query for UPS ... failed: ...), matching what Log NUT query failures and save the UPS Poller ID #58 did for NUT.error_message; before, a recovered apcupsd UPS kept showing its old error.:are no longer cut short (the old parser split on every:).Related Issue
Follow-up to the discussion about removing the
apcaccessrequirement; no issue filed.Motivation and Context
apcaccesswas the only thing the apcupsd collector needed on the poller, it needed a fixed binary path and a shell, and it made the collector Linux-only. The protocol is small, read-only and unauthenticated, so a native client is simpler and safer than shelling out.The
apcupsddaemon on the UPS host is still required, with its network information server enabled (NETSERVER on, port 3551 by default) and the poller able to reach it: open the NIS port to the poller in the UPS host's firewall (or use TCP Wrappers).NISIPis the interface the daemon listens on, not an allow-list, so it must not be set to the poller's address. The README now says so.How Has This Been Tested?
php -lis clean on every changed PHP file.NutClientTestalready produces.tests/Unit/NisClientTest.phpcovers unit stripping, record parsing, the session (framing, empty reply, early close, oversized record, runaway reply), the socket layer (a reply split across reads, hang-up, the overall deadline, a stall mid-record, TCP and refused connections, IPv6 bracketing) and the timeout bounds.locales/po/cacti.pothas the six new messages added and the removed one deleted.Not verified against a real
apcupsd. The tests use scripted servers, so they prove the code matches the protocol as I understand it (length-prefixed records, an empty record ends the reply), not that a real daemon agrees. Two details come from my reading of the protocol, not from apcupsd's source, which I couldn't reach: the empty-record terminator, and the exact list of unit wordsapcaccess -ustrips. Please check this draft against a real apcupsd host (compareapcaccess -uoutput with a poll) before it is marked ready.Types of changes
Checklist: