check / check (push) Waiting to run
bin/entrypoint.sh was top-level code with one helper; the shell section of the code style guide asks for every step in a function and a main called at the end. The PORT and TRUSTED_PROXIES checks, the data directory, starting each process, the wait loop, stopping both and the exit are now functions, which main calls in order, setting the stop trap before either process starts. It stays POSIX sh with no set -e. Messages and exit codes are unchanged; the 8081 message is passed to echo as two arguments so its line fits in 80 columns. Model: opus-5-5
163 lines
6.1 KiB
Bash
Executable File
163 lines
6.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# The container's entrypoint: runs netwatch-server and nginx side by
|
|
# side. TERM or INT stops both, and the container exits 0 if both exit
|
|
# cleanly. If either exits on its own, the other is stopped too and the
|
|
# container exits non-zero, so the platform restarts it instead of
|
|
# leaving it half up.
|
|
#
|
|
# No set -e: kill and wait return non-zero here in normal operation.
|
|
set -u
|
|
|
|
# PORT is the public port nginx listens on, 8080 when unset or empty.
|
|
# nginx would take a value such as localhost or unix:/tmp/x.sock as an
|
|
# address and start anyway, and reports a bad port without naming
|
|
# PORT, so a value that is not a usable port stops the container here,
|
|
# before either process starts.
|
|
check_port() {
|
|
export PORT="${PORT:-8080}"
|
|
case "$PORT" in
|
|
*[!0-9]*)
|
|
echo "entrypoint: PORT must be a port number, not '$PORT'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
# The length is checked first because, for a number too big for it,
|
|
# the shell's test prints an error and is false, so the range checks
|
|
# alone would let it through.
|
|
if [ "${#PORT}" -gt 5 ] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
|
|
echo "entrypoint: PORT must be from 1 to 65535, not '$PORT'" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$PORT" -eq 8081 ]; then
|
|
echo "entrypoint: PORT cannot be 8081, netwatch-server" \
|
|
"listens there" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# TRUSTED_PROXIES names the reverse proxies in front of the container,
|
|
# as IP addresses or CIDRs separated by commas. nginx takes the client
|
|
# address from X-Forwarded-For only on a request from one of them.
|
|
# Unset, they are the three private ranges of RFC 1918; set and empty,
|
|
# nginx trusts no one, which is why the default below follows - and not
|
|
# :-. configs/nginx.conf includes the file written here, one
|
|
# set_real_ip_from line per entry.
|
|
#
|
|
# nginx looks up an entry it cannot read as an address as a hostname,
|
|
# and trusts what it finds (1.2.3 is found as 1.2.0.3). So each entry
|
|
# is made a CIDR, a lone address getting /128 if it is IPv6 and /32 if
|
|
# not, and netwatch-server checks it with the parsing it gives its own
|
|
# TRUSTED_PROXIES. Its error, naming the CIDR, is dropped for the one
|
|
# below, naming the entry as written. set -f keeps a * in an entry from
|
|
# becoming a list of file names.
|
|
write_trusted_proxies() {
|
|
TRUSTED_PROXIES="${TRUSTED_PROXIES-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
|
|
set -f
|
|
for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do
|
|
case "$proxy" in
|
|
*/*) cidr="$proxy" ;;
|
|
*:*) cidr="$proxy/128" ;;
|
|
*) cidr="$proxy/32" ;;
|
|
esac
|
|
if ! netwatch-server check-cidr "$cidr" 2> /dev/null; then
|
|
echo "entrypoint: TRUSTED_PROXIES must be IP addresses or CIDRs" \
|
|
"separated by commas; '$proxy' is neither" >&2
|
|
exit 1
|
|
fi
|
|
echo "set_real_ip_from $cidr;"
|
|
done > /etc/nginx/trusted-proxies.conf
|
|
}
|
|
|
|
# netwatch-server keeps its report files in DATA_DIR, on the /data
|
|
# volume, which may be a host directory owned by root or by another
|
|
# uid. Here, as root, netwatch-server prepare-data-dir creates DATA_DIR
|
|
# and gives /data and the directories, regular files and symbolic links
|
|
# in it to the netwatch user, so the host directory needs no preparing.
|
|
# A file with a second name (hard link) keeps its owner, as that name
|
|
# could be outside /data, and its path is logged. It stops the start,
|
|
# naming DATA_DIR, unless DATA_DIR is /data or a path below it, and it
|
|
# acts on nothing outside /data, whatever symbolic or hard links it
|
|
# meets there.
|
|
prepare_data_dir() {
|
|
export DATA_DIR="${DATA_DIR:-/data/reports}"
|
|
netwatch-server prepare-data-dir "$DATA_DIR" || exit 1
|
|
}
|
|
|
|
# netwatch-server runs as the netwatch user and listens on loopback
|
|
# only, on a port other than the public one; configs/nginx.conf proxies
|
|
# to this address. Its only client is nginx, so it takes the client
|
|
# address nginx passes on from 127.0.0.1 alone, whatever TRUSTED_PROXIES
|
|
# the container has. The netwatch user has no login shell, hence -s
|
|
# /bin/sh. busybox su replaces itself with the command instead of
|
|
# staying on as its parent, so $! is the server's own PID.
|
|
start_backend() {
|
|
BIND_ADDRESS=127.0.0.1 PORT=8081 TRUSTED_PROXIES=127.0.0.1/32 \
|
|
su -s /bin/sh netwatch -c 'exec netwatch-server' &
|
|
backend=$!
|
|
}
|
|
|
|
# nginx starts through the nginx image's own entrypoint, which applies
|
|
# the image's start-up configuration and then replaces itself with
|
|
# nginx. Part of that start-up configuration renders configs/nginx.conf
|
|
# into conf.d with nginx listening on PORT. NGINX_ENVSUBST_FILTER limits
|
|
# that rendering to PORT: a variable nginx itself uses, such as $uri,
|
|
# would otherwise be replaced by an environment variable of the same
|
|
# name.
|
|
start_nginx() {
|
|
NGINX_ENVSUBST_FILTER='^PORT$' \
|
|
/docker-entrypoint.sh nginx -g 'daemon off;' &
|
|
nginx=$!
|
|
}
|
|
|
|
running() {
|
|
kill -0 "$1" 2>/dev/null
|
|
}
|
|
|
|
# POSIX sh cannot wait for whichever of two children exits first, so
|
|
# look once a second. The shell collects a child that has exited while
|
|
# it runs sleep, and running() is false for that child from then on.
|
|
wait_for_stop_or_exit() {
|
|
while [ -z "$stop_requested" ] && running "$backend" && running "$nginx"; do
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
# Stop both, then wait until neither is left.
|
|
stop_both() {
|
|
kill -TERM "$backend" "$nginx" 2>/dev/null
|
|
while running "$backend" || running "$nginx"; do
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
# Success is a requested stop that both processes exited cleanly from.
|
|
exit_with_status() {
|
|
wait "$backend"
|
|
backend_status=$?
|
|
wait "$nginx"
|
|
nginx_status=$?
|
|
echo "entrypoint: netwatch-server exited $backend_status," \
|
|
"nginx exited $nginx_status"
|
|
if [ -n "$stop_requested" ] && [ "$backend_status" -eq 0 ] &&
|
|
[ "$nginx_status" -eq 0 ]; then
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
}
|
|
|
|
main() {
|
|
check_port
|
|
write_trusted_proxies
|
|
prepare_data_dir
|
|
# A stop signal is only noted here; wait_for_stop_or_exit acts on it.
|
|
stop_requested=""
|
|
trap 'stop_requested=yes' TERM INT
|
|
start_backend
|
|
start_nginx
|
|
wait_for_stop_or_exit
|
|
stop_both
|
|
exit_with_status
|
|
}
|
|
|
|
main "$@"
|