1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
|
#!/bin/sh
# Shebang line included so editors and shellcheck/shfmt know this contains
# shell code
# Helper functions for NUT monitor (upsmon) configuration handling
# In recent (relevant) versions of shellcheck busybox is a valid shell type
# shellcheck shell=busybox
# Pre-requisite sourcing for functions this script uses
# * /lib/functions has been sourced
# * /lib/functions/nut/nut-common.sh has been sourced
# * /lib/functions/nut/nut-service.sh has been sourced
# Upstream NUT event type names (in the order listed in
# https://networkupstools.org/docs/man/upsmon.conf.html)
# NUT_EVENT_TYPES is used later without quotes for intentional word-splitting
# Globbing is disabled in that case
NUT_EVENT_TYPES="ONLINE ONBATT LOWBATT FSD COMMOK COMMBAD SHUTDOWN REPLBATT"
append NUT_EVENT_TYPES "NOCOMM NOPARENT CAL NOTCAL OFF NOTOFF BYPASS NOTBYPASS"
append NUT_EVENT_TYPES "ECO NOTECO OVER NOTOVER TRIM NOTTRIM BOOST NOTBOOST"
append NUT_EVENT_TYPES "OTHER NOTOTHER SUSPEND_STARTING SUSPEND_FINISHED"
# Upstream NUT upsmon.conf option names (in the order listed in
# https://networkupstools.org/docs/man/upsmon.conf.html)
# except MONITOR, NOTIFYMSG, NOTIFYFLAG, and RUN_AS_USER which are handled
# separately
NUT_UPSMON_OPTIONS="DEADTIME FINALDELAY HOSTSYNC MINSUPPLIES NOCOMMWARNTIME"
append NUT_UPSMON_OPTIONS "POLLFAIL_LOG_THROTTLE_MAX NOTIFYCMD POLLFREQ"
append NUT_UPSMON_OPTIONS "POLLFREQALERT POWERDOWNFLAG OFFDURATION OVERDURATION"
append NUT_UPSMON_OPTIONS "OBLBDURATION RBWARNTIME SHUTDOWNCMD SHUTDOWNEXIT"
append NUT_UPSMON_OPTIONS "CERTPATH CERTFILE CERTIDENT CERTHOST DEBUG_MIN"
NUT_UPSMON_BOOL_OPTIONS="ALARMCRITICAL CERTVERIFY FORCESSL"
# Location of NUT's UPS monitoring client (upsmon) configuration
UPSMON_C=/var/etc/nut/upsmon.conf
# Location of NUT's mode configuration
NUT_CONF=/var/etc/nut/nut.conf
# Path to PID file for upsmon
# shellcheck disable=SC2034
PIDFILE=/var/run/upsmon.pid
# config_load is done by the sourcing script, before executing any functions in
# this file
# Get notification configuration
# In nut_monitor UCI configuration a 'notification'-type config section must
# be named with the name of a NUT_EVENT_TYPE. This NUT_EVENT_TYPE maps to
# a notification event type emitted by upsmon.
nut_get_notifications() {
local event="$1"
local defaultnotify="$2"
local config_file="$3"
local event_types val
# Try to remove the name of the UCI section surrounded by spaces.
# If this differs from the NUT_EVENT_TYPE contents, then the section
# is a valid NUT event type, so use it.
event_types=" $NUT_EVENT_TYPES "
if [ "${event_types#*" $event "}" != "${event_types}" ]; then
config_get val "$event" message
if [ -n "$val" ]; then
val="$(printf '%s' "$val" | tr -d '"')"
if ! printf 'NOTIFYMSG %s "%s"\n' "$event" "$val" >>"$config_file"; then
log_error "upsmon section '$event' failed to write 'NOTIFYMSG' for '$event'" nut-monitor-config.sh nut-monitor-config
return 1
fi
fi
config_get val "$event" flag "$defaultnotify"
if [ -n "$val" ]; then
if ! printf 'NOTIFYFLAG %s %s\n' "$event" "$val" >>"$config_file"; then
log_error "upsmon section '$event' failed to write 'NOTIFYFLAG' for '$event'" nut-monitor-config.sh nut-monitor-config
return 1
fi
event_notify_flags_not_found="${event_notify_flags_not_found/$event/}"
fi
else
log_error "$event is not a valid NUT message event type" nut-monitor-config.sh nut-monitor-config
return 1
fi
}
upsmon_conf_get_write() {
local cfg="$1"
local config_file="$2"
local uci_option="$3"
local nut_option="$4"
local is_bool="$5" # optional parameter
local default="$6"
local val
if [ -n "$is_bool" ] && [ "$is_bool" = "true" ]; then
# Will always get a value - either 0 or 1
config_get_bool val "$cfg" "$uci_option" "$default"
else
config_get val "$cfg" "$uci_option" "$default"
fi
if [ -n "$val" ]; then
printf "%s %s\n" "$nut_option" "$val" >>"$config_file" || {
log_error "upsmon section '$cfg' failed to write '$nut_option'" nut-monitor-config.sh nut-monitor-config
return 1
}
return 0
else
# Will never trigger for a bool
return 2
fi
}
# Generate upsmon.conf
nut_upsmon_conf() {
local config_file="$1"
local cfg="upsmon"
local val defaultnotify nut_option uci_option conf_ret ssl_backend
local event_notify_flags_not_found upsmon_bool_options nut_bool
upsmon_bool_options=" $NUT_UPSMON_BOOL_OPTIONS "
ssl_backend="$(cat /usr/share/nut/ssl_backend)"
[ -n "$ssl_backend" ] || {
log_error_exit "Missing ssl_backend indicator. Bailing rather than running without SSL." "nut-monitor-config.sh" "nut-monitor-config"
return 1
}
# Note that we use '-u $RUNAS' on the daemon command line in preference to
# the RUN_AS_USER configuration in "$config_file"
# Word-splitting is intentional here, and we know NUT_UPSMON_OPTIONS and
# NUT_UPSMON_BOOL_OPTIONS are safe because we define them.
set -f
for nut_option in $NUT_UPSMON_OPTIONS $NUT_UPSMON_BOOL_OPTIONS; do
nut_bool="false"
# The nut_option and corresponding uci_option are known to be
# pure 7-bit ASCII
# We use tr because ash does not support *global* replacement using
# parameter expansion
# shellcheck disable=SC2019,SC2018
uci_option="$(echo "$nut_option" | tr 'A-Z_' 'a-z')"
case "$nut_option" in
# integer: negative values allowed
POLLFAIL_LOG_THROTTLE_MAX | \
OFFDURATION | \
OVERDURATION | \
OBLBDURATION)
config_get val "$cfg" "$uci_option"
if ! check_signed_int "$val"; then
log_error "upsmon section '$cfg' bad value for '$uci_option'" nut-monitor-config.sh nut-monitor-config
return 1
elif [ -n "$val" ]; then # Ignore options with no configuration
if ! printf "%s %s\n" "$nut_option" "$val" >>"$config_file"; then
log_error "upsmon section '$cfg' failed to write '$nut_option'" nut-monitor-config.sh nut-monitor-config
return 1
fi
fi
;;
# integer: negative values not allowed
DEADTIME | \
DEBUG_MIN | \
FINALDELAY | \
HOSTSYNC | \
MINSUPPLIES | \
NOCOMMWARNTIME | \
POLLFREQ | \
POLLFREQALERT | \
RBWARNTIME)
config_get val "$cfg" "$uci_option"
if ! check_unsigned_int "$val"; then
log_error "upsmon section '$cfg' bad value for '$uci_option'" nut-monitor-config.sh nut-monitor-config
return 1
elif [ -n "$val" ]; then # Ignore options with no configuration
if ! printf "%s %s\n" "$nut_option" "$val" >>"$config_file"; then
log_error "upsmon section '$cfg' failed to write '$nut_option'" nut-monitor-config.sh nut-monitor-config
return 1
fi
fi
;;
SHUTDOWNCMD)
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option"
conf_ret=$?
case "$conf_ret" in
1)
return 1
;;
2)
if ! printf "%s %s\n" "$nut_option" "/usr/sbin/nutshutdown" >>"$config_file"; then
log_error "upsmon section '$cfg' failed to write 'SHUTDOWNCMD'" nut-monitor-config.sh nut-monitor-config
return 1
fi
;;
esac
;;
ALARMCRITICAL)
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "true" "1"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
POWERDOWNFLAG)
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "false" "$NUT_KILLPOWER"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
CERTPATH | CERTIDENT | CERTHOST)
# if not compiled with SSL (OpenSSL or NSS), then do not attempt to use
# SSL configuration
if [ "$ssl_backend" != "openssl" ] && [ "$ssl_backend" != "nss" ]; then
continue
fi
# If no certpath or certfile is specified, then SSL will not be used, even if
# other SSL config exists
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "false"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
CERTVERIFY | FORCESSL)
# if not compiled with SSL (OpenSSL or NSS), then do not attempt to use
# SSL configuration
if [ "$ssl_backend" != "openssl" ] && [ "$ssl_backend" != "nss" ]; then
continue
fi
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "true"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
CERTFILE)
# if not compiled with OpenSSL, then do not attempt to use CERTFILE (it is
# OpenSSL specific)
if [ "$ssl_backend" != "openssl" ]; then
continue
fi
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "false"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
*)
if [ "${upsmon_bool_options/$nut_option/}" != "${upsmon_bool_options}" ]; then
nut_bool="true"
fi
upsmon_conf_get_write "$cfg" "$config_file" "$uci_option" "$nut_option" "$nut_bool"
conf_ret=$?
if [ "$conf_ret" -eq 1 ]; then
return 1
fi
;;
esac
done
set +f
event_notify_flags_not_found="$NUT_EVENT_TYPES"
config_get val "$cfg" defaultnotify "SYSLOG"
defaultnotify="$val"
config_foreach nut_get_notifications notifications "$val" "$config_file"
# Otherwise the default is WALL+SYSLOG
event_notify_flags_not_found="$(printf "%s" "$event_notify_flags_not_found" | tr -s ' ')"
# We intentionally word-split on event_notify_flags-not-found to iterate
# over event types.
set -f
for event in $event_notify_flags_not_found; do
if ! printf "NOTIFYFLAG %s %s\n" "$event" "$defaultnotify" >>"$config_file"; then
log_error "upsmon section '$cfg' failed to write 'NOTIFYFLAG' for '$event'" nut-monitor-config.sh nut-monitor-config
return 1
fi
done
set +f
return 0
}
nut_upsmon_add() {
local cfg="$1"
local config_file="$2"
local upsname
local hostname
local port
local powervalue
local username
local password
local system
local type
config_get upsname "$cfg" upsname "$cfg"
if ! check_safe_uci_name "$upsname"; then
log_error "upsmon section '$cfg' has invalid upsname" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
# we do not error exit so as not abort processing of other sections on the config_foreach
return 0
fi
config_get hostname "$cfg" hostname localhost
if ! check_safe_hostname_or_ip "$hostname"; then
log_error "upsmon section '$cfg' has invalid hostname" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
# we do not error exit so as not abort processing of other sections on the config_foreach
return 0
fi
config_get port "$cfg" port
if ! check_port "$port"; then
log_error "upsmon section '$cfg' has invalid port" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
# we do not error exit so as not abort processing of other sections on the config_foreach
return 0
fi
config_get powervalue "$cfg" powervalue 1
if ! check_unsigned_int "$powervalue" || [ -z "$powervalue" ]; then
log_error "upsmon section '$cfg' has invalid powervalue" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
# we do not error exit so as not abort processing of other sections on the config_foreach
return 0
fi
config_get username "$cfg" username
config_get password "$cfg" password
config_get type "$cfg" type secondary
case "$type" in
primary | secondary)
# primary or secondary are the only allowed values
:
;;
*)
log_error "upsmon section '$cfg' has invalid user/ups type" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
# we do not error exit so as not abort processing of other sections on the config_foreach
return 0
;;
esac
system="$upsname@$hostname"
if [ -n "$port" ]; then
system="$system:$port"
fi
if [ -z "$username" ] || [ -z "$password" ]; then
log_error "upsmon section '$cfg' missing value(s) for MONITOR line" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
else
# Write MONITOR line (including password) to config_file (upsmon.conf)
if ! printf "MONITOR %s %s %s %s %s\n" "$system" "$powervalue" "$username" "$password" "$type" >>"$config_file"; then
log_error "upsmon section '$cfg' failed to write MONITOR line for '$system'" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
else
have_monitor_line="true"
fi
fi
}
build_config() {
local conf_group
local upsmon_conf_fail="false"
local have_monitor_line="false"
RUNAS="${RUNAS:-nutmon}"
conf_group="$(id -gn "$RUNAS")"
if [ -z "$conf_group" ]; then
log_error "upsmon build_config failed to find group for the RUNAS user ('$RUNAS')" nut-monitor-config.sh nut-monitor-config
upsmon_conf_fail="true"
else
# This directory is shared with the nut-server which run as as a
# different user and group, so must be all readable. We set the
# ownership and permissions on individual files more restrictively, as
# needed.
# shellcheck disable=SC2174
umask 022
mkdir -p "$(dirname "$UPSMON_C")"
umask 127
touch "$UPSMON_C.new"
chgrp "$conf_group" "$UPSMON_C.new"
printf "%s\n" "# Config file automatically generated from UCI config" >>"$UPSMON_C.new"
if nut_upsmon_conf "$UPSMON_C.new"; then
# upsmon_conf_fail will be set in this function's context by nut_upsmon_add, on error
config_foreach nut_upsmon_add monitor "$UPSMON_C.new"
if [ "$upsmon_conf_fail" = "true" ]; then
log_error "'monitor' type sections must be correctly configured" nut-monitor-config.sh nut-monitor-config
return 1
fi
if [ "$have_monitor_line" = "false" ]; then
log_msg "Must have at least one 'monitor' type section" nut-monitor-config.sh nut-monitor-config warn
return 1
fi
else
log_error "upsmon section name 'upsmon' not correctly configured" nut-monitor-config.sh nut-monitor-config
return 1
fi
fi
# In the event of configuration failure, stop the
# service and remove the ephemeral configuration files
if [ "$upsmon_conf_fail" = "true" ]; then
# If we no longer have configuration, stop the service
return 1
else
# Atomically make the new config the active config
mv -f "$UPSMON_C.new" "$UPSMON_C" || return 1
fi
# Failure to write nut.conf is not hard-fatal although it means the NUT will
# not start the service.
# Also, we only write nut.conf if there is not one already
if [ ! -s "$NUT_CONF" ]; then
umask 133
if ! printf "MODE=netclient\n" >"$NUT_CONF"; then
log_error "upsmon creation of nut.conf failed" nut-monitor-config.sh nut-monitor-config
return 1
fi
else
# Otherwise if nut-server is already configured, make sure both
# nut-server and nut-monitor (this service) are started
if grep -q 'MODE=netserver' "$NUT_CONF"; then
# In modern OpenWrt 'sed -i' modifies the specified files, without backup
sed -i -e 's/netserver/both/' "$NUT_CONF" || {
log_error "Failed to update nut.conf to support both upsmon and upsd" nut-monitor-config.sh nut-monitor-config
}
fi
fi
return 0
}
|