Published: May 11, 2021 by luxagen
After 4-5 power failures over the last year, one of which corrupted a development repository, nuked my Git Extensions configuration, and interrupted external services, I decided I needed a UPS. I was recommended the BX500CI by a friend, but an extended period of Amazon stocklessness gave me time to realise that it wasn’t really right for my needs. Since I’m not always in the house, it wouldn’t help much for long outages because the equipment would merely suffer a delayed power loss if I wasn’t around to notice.
A search yielded the more expensive but connected BX950UI, with full support on Windows, GNU/Linux, and macOS via the excellent APC UPS Daemon, so I went for it.
Initial setup
On its arrival, I charged it and spent a couple of hours doing physical hookup, which involved replacing the plugs on a couple of multiway power strips with IEC C13s. This article and the apcupsd
manpage helped me get the basics going in another couple of hours, and I ran a battery calibration.
My theory is that this step doesn’t directly use power-consumption information, but records the drain curve of the battery with respect to total energy delivered to loads, allowing the UPS to correctly estimate remaining runtime for any load. I suspect that it’s worth recalibrating every 3 months or so, both to keep this profile updated and to give the battery itself enough exercise to avoid degrading too fast; I gather lead-acid batteries don’t like being left charged for too many months on end.
Fine-tuning
At first I began editing the /etc/apcupsd/apccontrol
script directly, but this file needs to be replaceable on package upgrade, so the correct approach is to edit the scripts specific to each event, e.g. /etc/apcupsd/doshutdown
. The next gotcha was that, not having read the above article properly, I missed the important step of setting ISCONFIGURED=yes
in /etc/default/apcupsd
, without which apcaccess
would work — but not the daemon itself. I was alerted to this problem by the smoking gun that neither the /var/log/apcupsd.events
nor /etc/apcupsd/powerfail
files were being created during testing.
As well as the Ubuntu server (an Intel NUC), I have a Windows 10 desktop attached to the UPS. Since the latter sucks enough juice to reduce battery runtime to about ten minutes, it was crucial to arrange for it to shut down quickly on power loss in order to maximise the server’s uptime. To this end I put the following command in the /etc/apcupsd/onbattery
script, just before the exit 0
line (to conceal any failure owing to e.g. the machine already being off), and using its IP address to dodge any transient name-resolution problems:
net rpc shutdown -t 45 -f -C "UPS shutdown" -I $IP_ADDRESS -U$USERNAME%$PASSWORD
This gives 45 seconds’ warning, just enough to run shutdown /a
if there’s a pressing need to use the machine. Testing revealed a few gotchas: the first was that the remote-shutdown feature apparently (see later) requires the Remote Registry service to be running, which got me from one error message to another.
The second problem was the need for UAC auto-elevation on RPC connections; I fixed that by using regedit
to create the DWORD value LocalAccountTokenFilterPolicy
=1 inside the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
key.
I later discovered that the Remote Registry service was set to Automatic but not actually started — and thus unnecessary. I left it on Automatic anyway and started it, because there’s a nasty impasse one can trip over during cloning/migration of Windows builds where logon is blocked owing to a faulty MountedDevices
configuration.
Another detail worth noting is that I don’t see the point of blocking remote logins on a GNU/Linux system that’s in the process of shutting down; it seems like a nannying feature to save admins from having their session nuked by the shutdown (not much of a benefit) and it takes away options in a time-critical situation. I therefore set this line in /etc/apcupsd/apcupsd.conf
:
NOLOGON disable
Final configuration
Here’s how it looks, along with my comments.
/etc/apcupsd/.gitignore
Since I run a Git repository in the Ubuntu server’s /etc
folder, the following gets rid of diff
noise:
apctest.output
powerfail
/etc/apcupsd/apccontrol
Since I don’t check root
’s mailbox on the server, I changed the export SYSADMIN
line to refer to my external e-mail address.
/etc/apcupsd/apcupsd.conf
Here I altered the following:
UPSNAME $NAME
UPSCABLE usb
UPSTYPE usb
DEVICE
POLLTIME 15
ONBATTERYDELAY 30
MINUTES 2
#BATTERYLEVEL 5
POLLTIME
is the maximum time (in seconds) that apcupsd
will take to notice a UPS event. I believe ONBATTERYDELAY
is the time in seconds between one of apcupsd
’s polls noticing power loss and the onbattery
state being triggered; this is how I prevent very short outages from shutting down the Windows machine. MINUTES
specifies how close to battery exhaustion the UPS can get before a shutdown is triggered; 2 minutes is generous for my server, which usually takes 10-20 seconds to shut down.
Using MINUTES
made BATTERYLEVEL
unnecessary, so I commented it out.
/etc/apcupsd/onbattery
Here I added the remote-shutdown command for the Windows machine (see above).
/etc/apcupsd/doshutdown
I replicated the Windows remote-shutdown command here (again before the exit 0
line to conceal failure) so that, even if I choose to keep the machine on during power loss via shutdown /a
, it will retry on battery exhaustion.
/etc/default/apcupsd
ISCONFIGURED=yes
Flaws and future work (or Things I Didn’t Have Time For)
One thing I must address is what I consider the biggest (only?) flaw in apcupsd
: its failure to provide handling for communications failures, which might sabotage the whole setup by hiding a power loss. In theory, this could be worked around with a bunch of custom scripting, but I think the real solution would lie in apcupsd
itself:
- introduce a
COMMFAILUREDELAY
setting that allows comms to recover within some timespan without generating an event; - change the comms-failure behaviour to (optionally?) trigger a shutdown.
The other major improvement to my setup would be to hibernate the machines in question instead of shutting them down. I looked into this for a while, but there are two problems:
- GNU/Linux requires a swap partition in order to hibernate;
- The Samba
net rpc shutdown
command provides only shutdown and restart, not sleep or hibernate.
I’ll probably deal with the former via an already-planned server rebuild, and the latter could be fixed by remotely invoking the native Windows SHUTDOWN
command instead of using the Samba one, but since Windows 10 no longer has an inbuilt Telnet server this would require extra software and time.
Conclusion
Thanks to this modest amount of work, and some testing — sometimes using the TIMEOUT
setting in /etc/apcupsd/apcupsd.conf
to avoid running the battery all the way down — I no longer have to worry about power loss in the middle of an intense work session, fiddling around with manual Git-repository repairs, or touring the local construction sites in a blame-seeking rage. Yay!