Entrypoint steps are functions called from main (closes #163) #180

Merged
clawbot merged 1 commits from issue-163-entrypoint-functions into next 2026-10-19 07:17:33 +02:00
2 changed files with 98 additions and 63 deletions
+7
View File
@@ -21,6 +21,13 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
# Completed Steps # Completed Steps
- 2026-10-19: `bin/entrypoint.sh` has each of its steps in a function, and a
`main` at the end calls them in order, as the shell section of the code style
guide asks ([#163](https://git.eeqj.de/sneak/netwatch/issues/163)). It is
still POSIX sh with no `set -e`, and does what it did, with the same messages
and exit codes. Checked by running the image with each script: a normal start,
bad `PORT`, `TRUSTED_PROXIES` and `DATA_DIR` values, `docker stop`, INT, and
nginx exiting on its own
- 2026-10-19: while report files cannot be written, the health check answers 503 - 2026-10-19: while report files cannot be written, the health check answers 503
with `"status":"error"` and the same fields, so the image's `HEALTHCHECK` with `"status":"error"` and the same fields, so the image's `HEALTHCHECK`
reports the container `unhealthy` reports the container `unhealthy`
+91 -63
View File
@@ -13,24 +13,27 @@ set -u
# address and start anyway, and reports a bad port without naming # 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, # PORT, so a value that is not a usable port stops the container here,
# before either process starts. # before either process starts.
export PORT="${PORT:-8080}" check_port() {
case "$PORT" in export PORT="${PORT:-8080}"
*[!0-9]*) case "$PORT" in
echo "entrypoint: PORT must be a port number, not '$PORT'" >&2 *[!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 exit 1
;; fi
esac if [ "$PORT" -eq 8081 ]; then
# The length is checked first because, for a number too big for it, echo "entrypoint: PORT cannot be 8081, netwatch-server" \
# the shell's test prints an error and is false, so the range checks "listens there" >&2
# alone would let it through. exit 1
if [ "${#PORT}" -gt 5 ] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then fi
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, # TRUSTED_PROXIES names the reverse proxies in front of the container,
# as IP addresses or CIDRs separated by commas. nginx takes the client # as IP addresses or CIDRs separated by commas. nginx takes the client
@@ -47,21 +50,23 @@ fi
# TRUSTED_PROXIES. Its error, naming the CIDR, is dropped for the one # 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 # below, naming the entry as written. set -f keeps a * in an entry from
# becoming a list of file names. # becoming a list of file names.
TRUSTED_PROXIES="${TRUSTED_PROXIES-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}" write_trusted_proxies() {
set -f TRUSTED_PROXIES="${TRUSTED_PROXIES-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"
for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do set -f
case "$proxy" in for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do
*/*) cidr="$proxy" ;; case "$proxy" in
*:*) cidr="$proxy/128" ;; */*) cidr="$proxy" ;;
*) cidr="$proxy/32" ;; *:*) cidr="$proxy/128" ;;
esac *) cidr="$proxy/32" ;;
if ! netwatch-server check-cidr "$cidr" 2> /dev/null; then esac
echo "entrypoint: TRUSTED_PROXIES must be IP addresses or CIDRs" \ if ! netwatch-server check-cidr "$cidr" 2> /dev/null; then
"separated by commas; '$proxy' is neither" >&2 echo "entrypoint: TRUSTED_PROXIES must be IP addresses or CIDRs" \
exit 1 "separated by commas; '$proxy' is neither" >&2
fi exit 1
echo "set_real_ip_from $cidr;" fi
done > /etc/nginx/trusted-proxies.conf echo "set_real_ip_from $cidr;"
done > /etc/nginx/trusted-proxies.conf
}
# netwatch-server keeps its report files in DATA_DIR, on the /data # 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 # volume, which may be a host directory owned by root or by another
@@ -73,12 +78,10 @@ done > /etc/nginx/trusted-proxies.conf
# naming DATA_DIR, unless DATA_DIR is /data or a path below it, and it # 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 # acts on nothing outside /data, whatever symbolic or hard links it
# meets there. # meets there.
export DATA_DIR="${DATA_DIR:-/data/reports}" prepare_data_dir() {
netwatch-server prepare-data-dir "$DATA_DIR" || exit 1 export DATA_DIR="${DATA_DIR:-/data/reports}"
netwatch-server prepare-data-dir "$DATA_DIR" || exit 1
# A stop signal is only noted here; the loop below acts on it. }
stop_requested=""
trap 'stop_requested=yes' TERM INT
# netwatch-server runs as the netwatch user and listens on loopback # netwatch-server runs as the netwatch user and listens on loopback
# only, on a port other than the public one; configs/nginx.conf proxies # only, on a port other than the public one; configs/nginx.conf proxies
@@ -87,9 +90,11 @@ trap 'stop_requested=yes' TERM INT
# the container has. The netwatch user has no login shell, hence -s # the container has. The netwatch user has no login shell, hence -s
# /bin/sh. busybox su replaces itself with the command instead of # /bin/sh. busybox su replaces itself with the command instead of
# staying on as its parent, so $! is the server's own PID. # staying on as its parent, so $! is the server's own PID.
BIND_ADDRESS=127.0.0.1 PORT=8081 TRUSTED_PROXIES=127.0.0.1/32 \ start_backend() {
su -s /bin/sh netwatch -c 'exec netwatch-server' & BIND_ADDRESS=127.0.0.1 PORT=8081 TRUSTED_PROXIES=127.0.0.1/32 \
backend=$! su -s /bin/sh netwatch -c 'exec netwatch-server' &
backend=$!
}
# nginx starts through the nginx image's own entrypoint, which applies # nginx starts through the nginx image's own entrypoint, which applies
# the image's start-up configuration and then replaces itself with # the image's start-up configuration and then replaces itself with
@@ -98,9 +103,11 @@ backend=$!
# that rendering to PORT: a variable nginx itself uses, such as $uri, # that rendering to PORT: a variable nginx itself uses, such as $uri,
# would otherwise be replaced by an environment variable of the same # would otherwise be replaced by an environment variable of the same
# name. # name.
NGINX_ENVSUBST_FILTER='^PORT$' \ start_nginx() {
/docker-entrypoint.sh nginx -g 'daemon off;' & NGINX_ENVSUBST_FILTER='^PORT$' \
nginx=$! /docker-entrypoint.sh nginx -g 'daemon off;' &
nginx=$!
}
running() { running() {
kill -0 "$1" 2>/dev/null kill -0 "$1" 2>/dev/null
@@ -109,26 +116,47 @@ running() {
# POSIX sh cannot wait for whichever of two children exits first, so # 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 # 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. # it runs sleep, and running() is false for that child from then on.
while [ -z "$stop_requested" ] && running "$backend" && running "$nginx"; do wait_for_stop_or_exit() {
sleep 1 while [ -z "$stop_requested" ] && running "$backend" && running "$nginx"; do
done sleep 1
done
}
# Stop both, then wait until neither is left. # Stop both, then wait until neither is left.
kill -TERM "$backend" "$nginx" 2>/dev/null stop_both() {
while running "$backend" || running "$nginx"; do kill -TERM "$backend" "$nginx" 2>/dev/null
sleep 1 while running "$backend" || running "$nginx"; do
done sleep 1
done
wait "$backend" }
backend_status=$?
wait "$nginx"
nginx_status=$?
echo "entrypoint: netwatch-server exited $backend_status," \
"nginx exited $nginx_status"
# Success is a requested stop that both processes exited cleanly from. # Success is a requested stop that both processes exited cleanly from.
if [ -n "$stop_requested" ] && [ "$backend_status" -eq 0 ] && exit_with_status() {
[ "$nginx_status" -eq 0 ]; then wait "$backend"
exit 0 backend_status=$?
fi wait "$nginx"
exit 1 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 "$@"