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 " - 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 " 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 " - Offer optional ntfy push notifications (self-hosted anywhere, or ntfy.sh)"
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.
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"
# 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
@@ -406,13 +437,16 @@ networks:
"
fi
# First instance keeps the exact existing Asterisk-exclusion port block
# (5000-5150 with 5038/5060/5061 carved out). An additional instance's
# range is shifted by 1000 per instance (computed above), which never
# lands on Asterisk's fixed ports, so it just publishes the plain range
# with no exclusions needed.
# Keyed on whether the range is still the unshifted default (5000), not
# on INSTANCE_SUFFIX — a first instance can also end up shifted now, if
# the live-collision check above moved it off 5000 (see that check's
# comment). Only the unshifted base range needs Asterisk's ports carved
# 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
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:
# they're Asterisk's ports (services/asterisk.sh runs Asterisk with
# 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
this right away, then go to Settings → Server → Permissions and uncheck
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/\`
- Database: PostgreSQL (\`$DB_CONTAINER\` container, data in \`db/\`)
- All database settings (name, user, password) live in \`.env\` — Traccar