summaryrefslogtreecommitdiffstats
path: root/net/nut/files/nut-server-config.sh.functions
blob: 1a5d36e92a52c5abc99f2ff57ad92fcaebb29413 (plain)
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
#!/bin/sh
# Shebang line included so editors and shellcheck/shfmt know this contains
# shell code

# Create configuration for upsd and UPS drivers for NUT

# 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.sh has been sourced
# * /lib/functions/nut/nut-service.sh has been sourced

# * /lib/functions/nut/nut-common.sh must be source and find_statepath executed
#   before build_config is called

# Ensure find_statepath from nut-service.sh is executed
# before srv_config in this file.

# Ephemeral files, recreated on each OpenWrt boot
USERS_C=/var/etc/nut/upsd.users # NUT user and password file
UPSD_C=/var/etc/nut/upsd.conf   # NUT upsd (UPS daemon) configuration
UPS_C=/var/etc/nut/ups.conf     # NUT UPS configuration (ini-style section per UPS)
NUT_CONF=/var/etc/nut/nut.conf  # NUT mode config

# config_load is done by the sourcing script, before executing any functions in
# this file

# Writes configuration values to the UPS run-time configuration file
get_write_ups_config() {
	local ups="$1"
	local var="$2"
	local def="$3"
	local flag="$4"
	local config_file="$5"
	local val

	if [ -z "$flag" ] || [ "$flag" = "0" ]; then
		config_get val "$ups" "$var" "$def"
		[ -n "$val" ] && printf "%s = %s\n" "$var" "$val" >>"$config_file"
	else
		config_get_bool val "$ups" "$var" "$def"
		# In NUT config a flag is either present or not present
		# present is true, not present is false. Map UCI bool to NUT flags.
		if [ "$val" = "1" ]; then
			printf "%s\n" "$var" >>"$config_file"
		fi
	fi
}

# Add upsd listen address and port to the run-time configuration
listen_address() {
	local srv="$1"
	local config_file="$2"
	local address port

	config_get address "$srv" address "::1"
	config_get port "$srv" port
	if [ -n "$port" ]; then
		printf "LISTEN %s %s\n" "$address" "$port" >>"$config_file"
	else
		printf "LISTEN %s\n" "$address" >>"$config_file"
	fi
}

# Adds upsd (NUT server) run-time configuration
srv_config() {
	local srv="$1"
	local config_file="$2"
	local maxage maxconn certfile

	config_get maxage "$srv" maxage
	[ -n "$maxage" ] && printf "MAXAGE %s\n" "$maxage" >>"$config_file"

	[ -n "$STATEPATH" ] && printf "STATEPATH %s\n" "$STATEPATH" >>"$config_file"

	config_get maxconn "$srv" maxconn
	[ -n "$maxconn" ] && printf "MAXCONN %s\n" "$maxconn" >>"$config_file"

	#NOTE: certs only apply to SSL-enabled version
	config_get certfile "$srv" certfile
	[ -n "$certfile" ] && printf "CERTFILE %s\n" "$certfile" >>"$config_file"
}

nut_user_instcmd() {
	local val="$1"
	local config_file="$2"
	printf "  instcmds = %s\n" "$val" >>"$config_file"
}

# Adds run-time configuration for NUT users to the upsd.users file
nut_user_add() {
	local user="$1"
	local config_file="$2"
	local a
	local val

	config_get val "$user" username "$user"
	printf "[%s]\n" "$val" >>"$config_file"

	config_get val "$user" password
	[ -n "$val" ] && printf "  password = %s\n" "$val" >>"$config_file"

	config_get val "$user" actions
	set -f
	for a in $val; do
		printf "  actions = %s\n" "$a" >>"$config_file"
	done
	set +f

	# The name instcmd is use for both the UCI option and the callback function
	config_list_foreach "$user" instcmd nut_user_instcmd "$config_file"

	config_get val "$user" upsmon
	if [ -n "$val" ]; then
		printf "  upsmon %s\n" "$val" >>"$config_file"
	fi
}

# Builds upsd (NUT server) run-time configuration
build_server_config() {
	local conf_group="$1"
	local nut_conf_err="false"

	umask 022
	mkdir -p "$(dirname "$UPSD_C")" || {
		log_error "Failed to create directory for upsd configuration file '$UPSD_C'" nut-server-config.sh nut-server-config
		return 1
	}

	rm -f "$USERS_C.new"
	rm -f "$UPSD_C.new"

	umask 117
	echo "# Config file automatically generated from UCI config" >"$USERS_C.new"
	echo "# Config file automatically generated from UCI config" >"$UPSD_C.new"
	restore_umask

	# For nut_user and listen_address, the section type and callback function
	# are named the same
	config_foreach nut_user_add user "$USERS_C.new"
	config_foreach listen_address listen_address "$UPSD_C.new"
	if have_section_of_type "upsd"; then
		config_foreach srv_config upsd "$UPSD_C.new"
	else
		# If config 'nut_server' does not have a 'upsd' section, use a default
		# configuration
		[ -n "$STATEPATH" ] && printf "STATEPATH %s\n" "$STATEPATH" >>"$UPSD_C.new"
	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" ] || grep -q '^MODE=none' "$NUT_CONF"; then
		umask 133
		if ! printf "MODE=netserver\n" >"$NUT_CONF"; then
			log_error "nut-server creation of nut.conf failed" nut-server-config.sh nut-server-config
			return 1
		fi
	else
		# Otherwise if nut-server is already configured, make sure both
		# nut-server (this service) and nut-monitor are started
		if grep -q '^MODE=netclient' "$NUT_CONF"; then
			# In modern OpenWrt 'sed -i' modifies the specified files, without backup
			sed -i -e 's/^MODE=netclient/MODE=both/' "$NUT_CONF" || {
				log_error "Failed to update nut.conf to support both upsmon and upsd" nut-server-config.sh nut-server-config
				nut_conf_err="true"
			}
		fi
	fi

	if [ "$nut_conf_err" = "false" ] && [ -n "$conf_group" ]; then
		chgrp "$conf_group" "$USERS_C.new" || {
			log_error "failed to set group on '$USERS_C.new'" nut-server-config.sh nut-server-config
			nut_conf_err="true"
		}
		chgrp "$conf_group" "$UPSD_C.new" || {
			log_error "failed to set group on '$UPSD_C.new'" nut-server-config.sh nut-server-config
			nut_conf_err="true"
		}
	fi

	# shellcheck disable=SC2034
	[ "$nut_conf_err" = "false" ] || return 1
}

# shellcheck disable=SC2317,SC2329
nut_ups_defoverride() {
	local overvar="$1"
	local defover="$2"
	local config_file="$3"
	local ups="$4"
	local overtype
	local overval

	# tr is used as global replace in variable parameter expansion is not
	# available in ash.
	overtype="$(printf "%s" "$overvar" | tr '_' '.')"

	config_get overval "$ups" "${defover}_${overvar}"
	[ -n "$overval" ] && printf "%s.%s = %s\n" "${defover}" "${overtype}" "$overval" >>"$config_file"
}

# From documentation and fixing of nut_ups_other (previously other or do_other)
# function (below) in https://github.com/rpavlik in
# https://github.com/openwrt/packages/pull/28308
#
# For each value "x" in the per-driver ($ups) list "other", get the value of
#   per-driver config variable "other_x", and if it is not empty,
#   write "x = {value of other_x}" to the config file.
# For each value "x" in the per-driver ($ups) list "otherflag", get the value of
#   per-driver config variable "otherflag_x", and if it is true, write
#  "x" to the config file.

# shellcheck disable=SC2317,SC2329
nut_ups_other() {
	local othervar="$1"
	local othervarflag="$2"
	local config_file="$3"
	local ups="$4"
	local otherval

	if [ "$othervarflag" = "otherflag" ]; then
		config_get_bool otherval "${ups}" "${othervarflag}_${othervar}"
		[ "$otherval" = "1" ] && printf "%s\n" "${othervar}" >>"$config_file"
	else
		config_get otherval "${ups}" "${othervarflag}_${othervar}"
		[ -n "$otherval" ] && printf "%s = %s\n" "${othervar}" "$otherval" >>"$config_file"
	fi
}

# Builds UPS-specific run-time configuration
build_ups_config() {
	local ups="$1"
	local config_file="$2"

	printf "[%s]\n" "$ups" >>"$config_file"

	get_write_ups_config "$ups" bus "" "" "$config_file"
	get_write_ups_config "$ups" cable "" "" "$config_file"
	get_write_ups_config "$ups" community "" "" "$config_file"
	get_write_ups_config "$ups" desc "" "" "$config_file"
	get_write_ups_config "$ups" driver "usbhid-ups" "" "$config_file"
	get_write_ups_config "$ups" ignorelb 0 1 "$config_file"
	get_write_ups_config "$ups" interruptonly 0 1 "$config_file"
	get_write_ups_config "$ups" interruptsize "" "" "$config_file"
	get_write_ups_config "$ups" maxreport "" "" "$config_file"
	get_write_ups_config "$ups" maxstartdelay "" "" "$config_file"
	get_write_ups_config "$ups" mfr "" "" "$config_file"
	get_write_ups_config "$ups" model "" "" "$config_file"
	get_write_ups_config "$ups" nolock 0 1 "$config_file"
	get_write_ups_config "$ups" notransferoids 0 1 "$config_file"
	get_write_ups_config "$ups" offdelay "" "" "$config_file"
	get_write_ups_config "$ups" ondelay "" "" "$config_file"
	get_write_ups_config "$ups" pollfreq "" "" "$config_file"
	get_write_ups_config "$ups" port "auto" "" "$config_file"
	get_write_ups_config "$ups" product "" "" "$config_file"
	get_write_ups_config "$ups" productid "" "" "$config_file"
	get_write_ups_config "$ups" retrydelay "" "" "$config_file"
	get_write_ups_config "$ups" sdorder "" "" "$config_file"
	get_write_ups_config "$ups" sdtime "" "" "$config_file"
	get_write_ups_config "$ups" serial "" "" "$config_file"
	get_write_ups_config "$ups" shutdown_delay "" "" "$config_file"
	get_write_ups_config "$ups" snmp_version "" "" "$config_file"
	get_write_ups_config "$ups" snmp_retries "" "" "$config_file"
	get_write_ups_config "$ups" snmp_timeout "" "" "$config_file"
	get_write_ups_config "$ups" synchronous "" "" "$config_file"
	get_write_ups_config "$ups" usd "" "" "$config_file"
	get_write_ups_config "$ups" vendor "" "" "$config_file"
	get_write_ups_config "$ups" vendorid "" "" "$config_file"

	# Params specific to NetXML driver
	get_write_ups_config "$ups" login "" "" "$config_file"
	get_write_ups_config "$ups" password "" "" "$config_file"
	get_write_ups_config "$ups" subscribe 0 1 "$config_file"

	config_list_foreach "$ups" override nut_ups_defoverride override "$config_file" "$ups"
	config_list_foreach "$ups" default nut_ups_defoverride default "$config_file" "$ups"

	# For each entry in the "$ups" list "other", get the
	# variable "other_${entry}", and if not empty, write
	# "${entry} = ${value of other_${entry}}" to the config file.
	config_list_foreach "$ups" other nut_ups_other other "$config_file" "$ups"

	# For each entry in the "$ups" list "otherflag", get the
	# variable "otherflag_${entry}", and if true, write "${entry}" to
	# the config file.
	config_list_foreach "$ups" otherflag nut_ups_other otherflag "$config_file" "$ups"

	echo "" >>"$config_file"
	# shellcheck disable=SC2034
	haveupscfg=true
}

# Add global drivers settings to run-time config
build_global_driver_config() {
	local cfg="$1"
	local config_file="$2"

	# Global driver config
	get_write_ups_config "$cfg" chroot "" "" "$config_file"
	get_write_ups_config "$cfg" driverpath "" "" "$config_file"
	get_write_ups_config "$cfg" maxstartdelay "" "" "$config_file"
	get_write_ups_config "$cfg" maxretry "" "" "$config_file"
	get_write_ups_config "$cfg" retrydelay "" "" "$config_file"
	get_write_ups_config "$cfg" pollinterval "" "" "$config_file"
	get_write_ups_config "$cfg" synchronous "" "" "$config_file"
	echo "" >>"$config_file"
}

# Orchestrate the run-time configuration building process
build_config() {
	local conf_group
	# 'pseudo-global' to allow config_foreach build_ups_config to signal that
	# at least one UPS section was configured. We do not want return 1 in
	# the config_foreach as that would abort processing for other UPS sections.
	local haveupscfg="false"

	if [ -z "$RUNAS" ]; then
		log_error "RUNAS is empty; cannot determine group for configuration files" nut-server-config.sh nut-server-config
		return 1
	fi

	if ! conf_group="$(id -gn "$RUNAS")"; then
		log_error "Failed to determine group for user '$RUNAS'" nut-server-config.sh nut-server-config
		return 1
	fi
	[ -n "$conf_group" ] || return 1

	# shellcheck disable=SC2153
	[ -d "$STATEPATH" ] || {
		umask 007
		mkdir -p "$STATEPATH" || {
			# We hard fail here, as we cannot rely on service state if
			# STATEPATH does not exist
			log_error "Failed to create ${STATEPATH}." nut-server nut-server
			# return so we can do cleanup
			return 1
		}
		if [ -n "$conf_group" ]; then
			chown "root:${conf_group}" "$STATEPATH" || {
				log_error "Failed to change owner:group on STATEPATH ('$STATEPATH')" nut-server-config.sh nut-server-config
				return 1
			}
		fi
	}

	# At this point we know STATEPATH exists and we have either created it with
	# the required owner, group, and permissions, or it was created elsewhere.
	# We do not second-guess other sources of creating this STATEPATH and
	# presume that as long as we have read and traversal access, that the
	# permissions are intentional. As 'stat' is not available by default in
	# OpenWrt we just check if we can read and traverse the directory.
	if [ ! -r "$STATEPATH" ] || [ ! -x "$STATEPATH" ]; then
		log_error "We do not have access to STATEPATH '$STATEPATH'." nut-server-config.sh nut-server-config
		return 1
	fi

	# This directory is shared with the nut-monitor which runs 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 "$UPS_C")"
	rm -f "$UPS_C"
	umask 137
	echo "# Config file automatically generated from UCI config" >"$UPS_C.new"
	chgrp "$conf_group" "$UPS_C.new" || {
		log_error "Failed to change group on UPS_C.new (new UPS config)" nut-server-config.sh nut-server-config
		return 1
	}

	config_foreach build_global_driver_config driver_global "$UPS_C.new"
	config_foreach build_ups_config driver "$UPS_C.new"

	if ! build_server_config "$conf_group"; then
		log_msg "Failed to configure upsd. Not starting." nut-server-config.sh nut-server-config warn
		return 1
	elif [ "$haveupscfg" = "false" ]; then
		log_msg "No configured UPS. Not starting upsd." nut-server-config.sh nut-server-config warn
		return 2
	fi
	return 0
}