Files
netbird-docs/docker/entrypoint.sh
Jack Carter 8528b632ad harden: non-root docs container with tini and a healthcheck (#844)
* harden: non-root container with tini, healthcheck, and a robust entrypoint

entrypoint.sh: escape sed metacharacters (\, &, #) so any future DocSearch value substitutes literally instead of crashing the pass or corrupting the bundle; rewrite only files that still contain the placeholder, making restarts no-ops; on substitution failure, log a warning and start the server anyway rather than crash-looping under restart: unless-stopped.

Dockerfile: run as the built-in non-root node user (artifacts chowned so the runtime sed keeps write access); add tini as PID 1 so SIGTERM actually reaches node and docker stop takes ~1s instead of the 10s kill grace; add a HEALTHCHECK so a dead server shows as unhealthy instead of silently Up.

Container-tested: boots 202ms, all routes/assets/redirects OK, metacharacter-laden values injected literally, uid 1000, health reaches healthy, restart idempotent (no rewrites, value intact), docker stop 0s.

* harden: substitute DocSearch placeholders independently; strip CR/LF from values

Review findings: the three substitutions were &&-chained, so one failing value skipped the remaining valid ones; and a raw newline in a value breaks the single-line sed command. Run each substitution independently, accumulating failures into one warning, and strip CR/LF in escape() (no legitimate DocSearch token contains them).

Container-tested: newline-laden appId is sanitized and all three placeholders still apply with no warning; forced full failure logs all three sed errors plus one warning and the server still starts healthy.

---------

Co-authored-by: Brandon Hopkins <brandon@techhut.tv>
2026-08-05 10:15:02 -07:00

48 lines
2.0 KiB
Bash

#!/bin/sh
# Substitutes the APP_NEXT_PUBLIC_DOCSEARCH_* placeholders baked into the
# client bundle at build time (from the committed .env) with the real values
# passed via the container environment, then starts the server.
#
# NEXT_PUBLIC_* values are compiled into the client bundle, so this rewrite is
# what lets one image serve any environment's DocSearch credentials.
set -eu
NEXT_PUBLIC_DOCSEARCH_APP_ID=${NEXT_PUBLIC_DOCSEARCH_APP_ID:-"none"}
NEXT_PUBLIC_DOCSEARCH_API_KEY=${NEXT_PUBLIC_DOCSEARCH_API_KEY:-"none"}
NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=${NEXT_PUBLIC_DOCSEARCH_INDEX_NAME:-"none"}
# Escape the characters that are special in a sed replacement (\ and &) and
# our s### delimiter (#), so values containing them substitute literally.
# CR/LF are stripped first: a one-line s### command cannot carry a raw
# newline, and no legitimate DocSearch token contains one.
escape() {
printf '%s' "$1" | tr -d '\r\n' | sed -e 's/[\\&#]/\\&/g'
}
# Rewrite only the files that still contain the placeholder — after the first
# boot substituted everything, restarts touch nothing. grep exiting 1 on zero
# matches is fine: xargs -r then runs nothing and the pipeline succeeds.
substitute() {
grep -rlZ "$1" /usr/app/.next | xargs -0 -r sed -i "s#$1#$2#g"
}
# Each substitution runs independently: one failing value must not stop the
# remaining placeholders from being applied.
ok=1
substitute APP_NEXT_PUBLIC_DOCSEARCH_APP_ID "$(escape "$NEXT_PUBLIC_DOCSEARCH_APP_ID")" || ok=0
substitute APP_NEXT_PUBLIC_DOCSEARCH_API_KEY "$(escape "$NEXT_PUBLIC_DOCSEARCH_API_KEY")" || ok=0
substitute APP_NEXT_PUBLIC_DOCSEARCH_INDEX_NAME "$(escape "$NEXT_PUBLIC_DOCSEARCH_INDEX_NAME")" || ok=0
if [ "$ok" = 1 ]; then
echo "DocSearch configuration applied"
else
# Serve the docs even if search wiring failed — a docs site with broken
# search beats a crash-looping container. The warning makes it visible.
echo "WARNING: DocSearch placeholder substitution failed; search may be broken" >&2
fi
echo "starting Next.js"
exec "$@"