@@ -1,14 +1,14 @@
#!/bin/bash
# NetAlertX Root-Priming Entrypoint — best-effort permission priming 🔧
#
# Purpose :
# Responsibilities :
# - Provide a runtime, best-effort remedy for host volume ownership/mode issues
# (common on appliances like Synology where Docker volume copy‑ up is limited).
# - Ensure writable paths exist, attempt to `chown`/`chmod` to a runtime `PUID`/`PGID`
# - Ensure writable paths exist, attempt to `chown` to a runtime `PUID`/`PGID`
# (defaults to 20211), then drop privileges via `su-exec` if possible.
#
# Design & behavior notes:
# - This script is intentionally *non-fatal* for chown/chmod failures; operations are
# - This script is intentionally *non-fatal* for chown failures; operations are
# best-effort so we avoid blocking container startup on imperfect hosts.
# - Runtime defaults are used so the image works without requiring build-time args.
# - If the container is started as non-root (`user:`), priming is skipped and it's the
@@ -16,42 +16,60 @@
# - If `su-exec` cannot drop privileges, we log a note and continue as the current user
# rather than aborting (keeps first-run resilient).
#
# Operat ion al recomme nda tion:
# - For deterministic ownership, explicitly set `PUID`/`PGID` (or pre-chown host volumes),
# and when hardening capabilities ad d ` cap_add: [CHOWN]` so priming can succeed .
# Behav ior al co ndi tions :
# 1. RUNTIME: NON-ROOT (Container started as user: 1000)
# - PUID/PGID env vars are ignore d ( cannot switch users) .
# - Write permissions check performed on /data and /tmp.
# - EXEC: Direct entrypoint execution as current user.
#
# 2. RUNTIME: ROOT (Container started as user: 0)
# A. TARGET: PUID=0 (User requested root)
# - Permissions priming skipped (already root).
# - EXEC: Direct entrypoint execution as root (with security warning).
#
# B. TARGET: PUID > 0 (User requested privilege drop)
# - PRIMING: Attempt chown on /data & /tmp to PUID:PGID.
# (Failures logged but non-fatal to support NFS/ReadOnly mounts).
# - EXEC: Attempt `su-exec PUID:PGID`.
# - Success: Process runs as PUID.
# - Failure (Missing CAPS): Fallback to running as root to prevent crash.
# - If PUID=0, log a warning and run directly.
# - Otherwise, attempt to prime paths and `su-exec` to PUID:PG
PUID = " ${ PUID :- ${ NETALERTX_UID :- 20211 } } "
PGID = " ${ PGID :- ${ NETALERTX_GID :- 20211 } } "
# Pretty terminal colors used for fatal messages (kept minimal + POSIX printf)
RED = $( printf '\033[1;31m' )
RESET = $( printf '\033[0m' )
_error_msg( ) {
title = " $1 "
body = " $2 "
>& 2 printf "%s" " ${ RED } "
>& 2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
🔒 SECURITY - FATAL: ${title}
${body}
══════════════════════════════════════════════════════════════════════════════
EOF
>& 2 printf "%s" " ${ RESET } "
}
_validate_id( ) {
value = " $1 "
name = " $2 "
if ! printf '%s' " ${ value } " | grep -qxE '[0-9]+' ; then
>& 2 printf "%s" " ${ RED } "
>& 2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
🔒 SECURITY - FATAL: invalid ${name} value (non-numeric)
_error_msg " INVALID ${ name } VALUE (non-numeric) " \
" Startup halted because the provided ${ name } environmental variable
contains non-digit characters.
Startup halted because the provided ${name} environmental variable
contains non-digit characters. This is a deliberate security measure to
prevent environment-variable command injection while the container runs as
root during initial startup.
Action: set a numeric ${name} (for example: PUID=1000) in your environment
or docker-compose file and restart the container. Default: 20211.
For more information and troubleshooting, see:
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/PUID_PGID_SECURITY.md
══════════════════════════════════════════════════════════════════════════════
EOF
>& 2 printf "%s" " ${ RESET } "
exit 1
Action: set a numeric ${ name } (for example: ${ name } =1000) in your environment
or docker-compose file. Default: 20211. "
exit 1
fi
}
@@ -61,25 +79,29 @@ _validate_id "${PGID}" "PGID"
_cap_bits_warn_missing_setid( ) {
cap_hex = $( awk '/CapEff/ {print $2}' /proc/self/status 2>/dev/null || echo "" )
[ -n " ${ cap_hex } " ] || return
cap_dec = $(( 0 x${ cap_hex } ))
has_setgid = 0; has_setuid = 0; has_net_caps = 0
# POSIX compliant base16 on permissions
cap_dec = $( awk 'BEGIN { h = "0x' " ${ cap_hex } " '"; if (h ~ /^0x[0-9A-Fa-f]+$/) { printf "%d", h } else { print 0 } }' )
# Bit masks (use numeric constants to avoid editor/HL issues and improve clarity)
# 1 << 6 = 64
# 1 << 7 = 128
# (1<<10)|(1<<12)|(1<<13) = 1024 + 4096 + 8192 = 13312
SETGID_MASK = 64
SETUID_MASK = 128
NET_MASK = 13312
has_setgid = 0
has_setuid = 0
has_net_caps = 0
if [ $(( cap_dec & ( 1 << 6 )) ) -ne 0 ] ; then
if ( ( cap_dec & SETGID_MASK ) ) ; then
has_setgid = 1
fi
if [ $( ( cap_dec & ( 1 << 7))) -ne 0 ]; then
if ( ( cap_dec & SETUID_MASK ) ) ; then
has_setuid = 1
fi
if [ $((cap_dec & (1 << 10))) -ne 0 ] || [ $((cap_dec & (1 << 12))) -ne 0 ] || [ $((cap_dec & (1 << 13))) -ne 0 ]; then
if ( ( cap_dec & NET_MASK ) ) ; then
has_net_caps = 1
fi
if [ "${ has_net_caps}" -eq 1 ] && { [ "${has_setgid}" -eq 0 ] || [ "${has_setuid}" -eq 0 ]; }; then
if ( ( has_net_caps = = 1 && ( has_setgid = = 0 || has_setuid = = 0 ) ) ) ; then
>& 2 echo "Note: CAP_SETUID/CAP_SETGID unavailable alongside NET_* caps; continuing as current user."
fi
}
@@ -87,15 +109,29 @@ _cap_bits_warn_missing_setid() {
_cap_bits_warn_missing_setid
if [ " $( id -u) " -ne 0 ] ; then
if [ -n "${PUID:-}" ] || [ -n "${PGID:-}" ]; then
>&2 printf 'Note: container running as UID %s GID %s; requested PUID/PGID=%s:%s will not be applied.\n' \
"$(id -u)" "$(id -g)" "${PUID}" "${PGID}"
for path in "/tmp" " ${ NETALERTX_DATA :- /data } " ; do
if [ -n " $path " ] && [ ! -w " $path " ] ; then
_error_msg "FILESYSTEM PERMISSIONS ERROR" \
" Container is running as User $( id -u) , but cannot write to:
${ path }
Because the container is not running as root, it cannot fix these
permissions automatically.
Action:
1. Update Host Volume permissions (e.g. 'chmod 755 ${ path } ' on host).
2. Or, run container as root (user: 0) and let PUID/PGID logic handle it. "
fi
done
if [ -n " ${ PUID :- } " ] && [ " ${ PUID } " != " $( id -u) " ] ; then
>& 2 printf 'Note: container running as UID %s; requested PUID=%s ignored.\n' " $( id -u) " " ${ PUID } "
fi
exec /entrypoint.sh " $@ "
fi
if [ " ${ PUID } " -eq 0 ] ; then
>&2 echo "WARNING: Running as root (PUID=0). Prefer a non-root PUID. See https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/file-permissions.md "
>& 2 echo "WARNING: Running as root (PUID=0). Prefer a non-root PUID."
exec /entrypoint.sh " $@ "
fi
@@ -103,28 +139,26 @@ _prime_paths() {
runtime_root = " ${ NETALERTX_RUNTIME_BASE :- /tmp } "
paths = " /tmp ${ NETALERTX_DATA :- /data } ${ NETALERTX_CONFIG :- /data/config } ${ NETALERTX_DB :- /data/db } ${ NETALERTX_LOG :- ${ runtime_root } /log } ${ NETALERTX_PLUGINS_LOG :- ${ runtime_root } /log/plugins } ${ NETALERTX_API :- ${ runtime_root } /api } ${ SYSTEM_SERVICES_RUN :- ${ runtime_root } /run } ${ SYSTEM_SERVICES_RUN_TMP :- ${ runtime_root } /run/tmp } ${ SYSTEM_SERVICES_RUN_LOG :- ${ runtime_root } /run/logs } ${ SYSTEM_SERVICES_ACTIVE_CONFIG :- ${ runtime_root } /nginx/active-config } ${ runtime_root } /nginx "
chmod 17 77 / tmp 2 >/dev/null | | true
# Always chown core roots up front so non-root runtime can chmod later.
chown -R " ${ PUID } : ${ PGID } " /data 2>/dev/null || true
chown -R " ${ PUID } : ${ PGID } " /tmp 2>/dev/null || true
for path in ${ paths } ; do
[ -n " ${ path } " ] || continue
if [ " ${ path } " = "/tmp" ] ; then
contin ue
fi
install - d - o " ${ PUID } " - g " ${ PGID } " - m 700 " ${ path } " 2 >/dev/null | | true
if [ " ${ path } " = "/tmp" ] ; then continue ; fi
install -d -o " ${ PUID } " -g " ${ PGID } " " ${ path } " 2>/dev/null || tr ue
chown -R " ${ PUID } : ${ PGID } " " ${ path } " 2>/dev/null || true
chmod - R u+rwX " ${ path } " 2 >/dev/null | | true
# Note: chown must be done by root, chmod can be done by non-root
# (chmod removed as non-root runtime will handle modes after ownership is set)
done
>& 2 echo " Permissions prepared for PUID= ${ PUID } . "
}
_prime_paths
unset NETALERTX_PRIVDROP_FAILED
if ! su-exec " ${ PUID } : ${ PGID } " /entrypoint.sh " $@ " ; then
rc = $?
export NETALERTX_PRIVDROP_FAILED = 1
export NETALERTX_CHECK_ONLY = " ${ NETALERTX_CHECK_ONLY :- 1 } "
export NETALERTX_CHECK_ONLY = " ${ NETALERTX_CHECK_ONLY :- 0 } "
>& 2 echo " Note: su-exec failed (exit ${ rc } ); continuing as current user without privilege drop. "
exec /entrypoint.sh " $@ "
fi