Live-scan Traccar's device-protocol range for collisions, not just Asterisk's ports

The 5000-5150 range was only ever checked against Asterisk's hardcoded
fixed ports (5038/5060/5061) for a first instance — no live scan of the
rest of the range, because the directory-count-based offset mechanism
only triggers for an explicit additional instance.

Confirmed live: this range sat unclaimed at the OS level while this
Traccar instance's container had never actually started, so an
unrelated service's own find_free_port scan found port 5007 genuinely
free (nothing was listening there yet) and took it — invisible to any
check until Traccar itself tried to bind its declared range for the
first time, failing with "port is already allocated".

Add a live scan across the whole intended range (skipping Asterisk's
expected carve-outs at the base 5000-5150 range) and shift by 1000,
same step the multi-instance path already uses, until genuinely clear.
Also fixed the compose-block and README generation, which keyed off
INSTANCE_SUFFIX being empty to decide whether Asterisk's exclusions
were needed — now keyed off whether the range is still the unshifted
default (PROTO_MIN -eq 5000), since a first instance can now end up
shifted too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
Claude
2026-08-11 02:43:37 +00:00
parent 8bdf4c0a07
commit 77440c8f75
+42 -8
View File
@@ -231,7 +231,8 @@ install_traccar() {
echo " - Point Traccar at it via env vars (CONFIG_USE_ENVIRONMENT_VARIABLES) — no secrets in a config file" echo " - Point Traccar at it via env vars (CONFIG_USE_ENVIRONMENT_VARIABLES) — no secrets in a config file"
echo " - Deploy an autoheal container that restarts Traccar if its healthcheck fails" echo " - Deploy an autoheal container that restarts Traccar if its healthcheck fails"
echo " - Expose port 8082 (web) and 5000-5150 (device protocols; 5038/5060/5061 skipped — Asterisk keeps" echo " - Expose port 8082 (web) and 5000-5150 (device protocols; 5038/5060/5061 skipped — Asterisk keeps"
echo " priority on those); an additional instance's device-protocol range shifts by 1000 instead" echo " priority on those); shifts by 1000 instead, whether for an additional instance or because"
echo " something else already has a port in that range"
echo " - No default login — register the first account at the web UI, it becomes admin" echo " - No default login — register the first account at the web UI, it becomes admin"
echo " - Offer optional ntfy push notifications (self-hosted anywhere, or ntfy.sh)" echo " - Offer optional ntfy push notifications (self-hosted anywhere, or ntfy.sh)"
echo " - Offer a Caddy reverse proxy and to start the container" echo " - Offer a Caddy reverse proxy and to start the container"
@@ -310,6 +311,36 @@ install_traccar() {
# directory-count-based mechanism (not an ss scan) and is unaffected. # directory-count-based mechanism (not an ss scan) and is unaffected.
find_free_port WEB_PORT "$WEB_PORT" find_free_port WEB_PORT "$WEB_PORT"
# Live-check the whole device-protocol range too — the directory-count
# offset above only avoids colliding with an *earlier Traccar instance*,
# not an unrelated single-port service. Confirmed live: this range sat
# unclaimed at the OS level while this Traccar instance's own container
# had never actually started (still `docker compose up`'d for the first
# time), so a completely unrelated service's own find_free_port scan
# found 5007 "free" (nothing was listening there yet) and took it —
# invisible to any check until Traccar itself actually tried to bind
# its declared range. Shift by 1000 (reusing the same offset step the
# multi-instance path uses) until the whole range is genuinely clear.
_traccar_range_conflict() {
local min="$1" max="$2" p
for ((p = min; p <= max; p++)); do
# The base 5000-5150 range's own carve-outs for Asterisk are
# expected occupants, not a conflict — only relevant at the
# unshifted range; a shifted range never overlaps them anyway.
if [ "$min" -eq 5000 ] && { [ "$p" -eq 5038 ] || [ "$p" -eq 5060 ] || [ "$p" -eq 5061 ]; }; then
continue
fi
port_in_use "$p" tcp && return 0
port_in_use "$p" udp && return 0
done
return 1
}
while _traccar_range_conflict "$PROTO_MIN" "$PROTO_MAX"; do
log_warning "Device-protocol range $PROTO_MIN-$PROTO_MAX collides with something already running — shifting to $((PROTO_MIN + 1000))-$((PROTO_MAX + 1000))."
PROTO_MIN=$((PROTO_MIN + 1000))
PROTO_MAX=$((PROTO_MAX + 1000))
done
mkdir -p "$TRACCAR_DIR" mkdir -p "$TRACCAR_DIR"
# Non-recursive on purpose — a rerun already has a `db/` full of Postgres's # Non-recursive on purpose — a rerun already has a `db/` full of Postgres's
# own data files, owned by whatever uid the postgres container runs as # own data files, owned by whatever uid the postgres container runs as
@@ -406,13 +437,16 @@ networks:
" "
fi fi
# First instance keeps the exact existing Asterisk-exclusion port block # Keyed on whether the range is still the unshifted default (5000), not
# (5000-5150 with 5038/5060/5061 carved out). An additional instance's # on INSTANCE_SUFFIX — a first instance can also end up shifted now, if
# range is shifted by 1000 per instance (computed above), which never # the live-collision check above moved it off 5000 (see that check's
# lands on Asterisk's fixed ports, so it just publishes the plain range # comment). Only the unshifted base range needs Asterisk's ports carved
# with no exclusions needed. # out; any shifted range (whether from a genuine additional instance or
# a first instance that got bumped for a live collision) never lands on
# Asterisk's fixed ports, so it just publishes the plain range with no
# exclusions needed.
local _PROTO_PORT_BLOCK local _PROTO_PORT_BLOCK
if [ -z "$INSTANCE_SUFFIX" ]; then if [ "$PROTO_MIN" -eq 5000 ]; then
_PROTO_PORT_BLOCK=" # 5038 (AMI), 5060 (SIP, tcp+udp), and 5061 (SIP TLS, tcp) are skipped: _PROTO_PORT_BLOCK=" # 5038 (AMI), 5060 (SIP, tcp+udp), and 5061 (SIP TLS, tcp) are skipped:
# they're Asterisk's ports (services/asterisk.sh runs Asterisk with # they're Asterisk's ports (services/asterisk.sh runs Asterisk with
# network_mode: host, so it binds them directly on the host, not # network_mode: host, so it binds them directly on the host, not
@@ -584,7 +618,7 @@ Traccar instance.")
stays open to anyone who reaches this server until you turn it off, so do stays open to anyone who reaches this server until you turn it off, so do
this right away, then go to Settings → Server → Permissions and uncheck this right away, then go to Settings → Server → Permissions and uncheck
Registration. Registration.
- Device protocols: ports ${PROTO_MIN}-${PROTO_MAX} (TCP + UDP$( [ -z "$INSTANCE_SUFFIX" ] && echo "; 5038/tcp, 5060/tcp+udp, and 5061/tcp are skipped — reserved for Asterisk's AMI and SIP if this box also runs Asterisk from this repo, which gets priority on those ports")) - Device protocols: ports ${PROTO_MIN}-${PROTO_MAX} (TCP + UDP$( [ "$PROTO_MIN" -eq 5000 ] && echo "; 5038/tcp, 5060/tcp+udp, and 5061/tcp are skipped — reserved for Asterisk's AMI and SIP if this box also runs Asterisk from this repo, which gets priority on those ports"))
- App data: \`data/\` and \`logs/\` - App data: \`data/\` and \`logs/\`
- Database: PostgreSQL (\`$DB_CONTAINER\` container, data in \`db/\`) - Database: PostgreSQL (\`$DB_CONTAINER\` container, data in \`db/\`)
- All database settings (name, user, password) live in \`.env\` — Traccar - All database settings (name, user, password) live in \`.env\` — Traccar