Skip to content

Commit 2d09767

Browse files
committed
ccache-remote: improve Docker loopback handling and IPv6 host parsing
1 parent d6c937c commit 2d09767

File tree

1 file changed

+119
-14
lines changed

1 file changed

+119
-14
lines changed

extensions/ccache-remote/ccache-remote.sh

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,36 @@ declare -g -a CCACHE_PASSTHROUGH_VARS=(
123123
CCACHE_PCH_EXTSUM
124124
)
125125

126+
# Format host:port, wrapping IPv6 addresses in brackets for URL compatibility (RFC 2732)
127+
function ccache_format_host_port() {
128+
local host="$1" port="$2"
129+
if [[ "${host}" == *:* ]]; then
130+
echo "[${host}]:${port}"
131+
else
132+
echo "${host}:${port}"
133+
fi
134+
}
135+
136+
# Extract hostname from CCACHE_REMOTE_STORAGE URL (strips scheme, userinfo, port, path)
137+
function ccache_extract_url_host() {
138+
local url="$1"
139+
local after_scheme="${url#*://}"
140+
# Strip userinfo if present
141+
if [[ "${after_scheme}" == *@* ]]; then
142+
after_scheme="${after_scheme##*@}"
143+
fi
144+
local host
145+
# Handle bracketed IPv6: [addr]:port
146+
if [[ "${after_scheme}" == \[* ]]; then
147+
host="${after_scheme#\[}"
148+
host="${host%%\]*}"
149+
else
150+
# Strip port, path, and ccache attributes
151+
host="${after_scheme%%[:\/|]*}"
152+
fi
153+
echo "${host}"
154+
}
155+
126156
# Discover ccache remote storage via DNS-SD (mDNS/Avahi) or DNS SRV records.
127157
# Looks for _ccache._tcp services with TXT records: type=http|redis, path=/...
128158
# Prefers Redis over HTTP when multiple services are found.
@@ -136,6 +166,7 @@ function ccache_discover_remote_storage() {
136166
# Parse resolved lines: =;IFACE;PROTO;NAME;TYPE;DOMAIN;HOSTNAME;ADDRESS;PORT;"txt"...
137167
# Prefer IPv4 (proto=IPv4), prefer type=redis over type=http
138168
local redis_url="" http_url=""
169+
local redis_host="" redis_host_ip="" http_host="" http_host_ip=""
139170
while IFS=';' read -r status iface proto name stype domain hostname address port txt_rest; do
140171
[[ "${status}" == "=" && "${proto}" == "IPv4" ]] || continue
141172
local svc_type="" svc_path=""
@@ -146,19 +177,31 @@ function ccache_discover_remote_storage() {
146177
if [[ "${txt_rest}" =~ \"path=([^\"]+)\" ]]; then
147178
svc_path="${BASH_REMATCH[1]}"
148179
fi
180+
# Use hostname for URL (Docker --add-host resolves it), fall back to address
181+
local svc_host="${hostname%.local}"
182+
svc_host="${svc_host%.}"
183+
[[ -z "${svc_host}" ]] && svc_host="${address}"
149184
if [[ "${svc_type}" == "redis" ]]; then
150-
redis_url="redis://${address}:${port}|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
185+
redis_url="redis://${svc_host}:${port}|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
186+
redis_host="${svc_host}"
187+
redis_host_ip="${address}"
151188
elif [[ "${svc_type}" == "http" ]]; then
152-
http_url="http://${address}:${port}${svc_path}"
189+
http_url="http://${svc_host}:${port}${svc_path}"
190+
http_host="${svc_host}"
191+
http_host_ip="${address}"
153192
fi
154193
done <<< "${browse_output}"
155-
# Redis preferred over HTTP
194+
# Redis preferred over HTTP; set hostname->IP mapping for Docker --add-host
156195
if [[ -n "${redis_url}" ]]; then
157196
export CCACHE_REMOTE_STORAGE="${redis_url}"
197+
declare -g CCACHE_REMOTE_HOST="${redis_host}"
198+
declare -g CCACHE_REMOTE_HOST_IP="${redis_host_ip}"
158199
display_alert "DNS-SD: discovered Redis ccache" "$(ccache_mask_storage_url "${CCACHE_REMOTE_STORAGE}")" "info"
159200
return 0
160201
elif [[ -n "${http_url}" ]]; then
161202
export CCACHE_REMOTE_STORAGE="${http_url}"
203+
declare -g CCACHE_REMOTE_HOST="${http_host}"
204+
declare -g CCACHE_REMOTE_HOST_IP="${http_host_ip}"
162205
display_alert "DNS-SD: discovered HTTP ccache" "$(ccache_mask_storage_url "${CCACHE_REMOTE_STORAGE}")" "info"
163206
return 0
164207
fi
@@ -184,10 +227,12 @@ function ccache_discover_remote_storage() {
184227
if [[ "${txt_output}" =~ path=([^\"[:space:]]+) ]]; then
185228
svc_path="${BASH_REMATCH[1]}"
186229
fi
230+
local host_port
231+
host_port=$(ccache_format_host_port "${srv_host}" "${srv_port}")
187232
if [[ "${svc_type}" == "http" ]]; then
188-
export CCACHE_REMOTE_STORAGE="http://${srv_host}:${srv_port}${svc_path}"
233+
export CCACHE_REMOTE_STORAGE="http://${host_port}${svc_path}"
189234
else
190-
export CCACHE_REMOTE_STORAGE="redis://${srv_host}:${srv_port}|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
235+
export CCACHE_REMOTE_STORAGE="redis://${host_port}|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
191236
fi
192237
display_alert "DNS SRV: discovered ccache" "$(ccache_mask_storage_url "${CCACHE_REMOTE_STORAGE}")" "info"
193238
return 0
@@ -199,7 +244,9 @@ function ccache_discover_remote_storage() {
199244
local ccache_ip
200245
ccache_ip=$(getent hosts ccache.local 2>/dev/null | awk '{print $1; exit}' || true)
201246
if [[ -n "${ccache_ip}" ]]; then
202-
export CCACHE_REMOTE_STORAGE="redis://${ccache_ip}:6379|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
247+
local host_port
248+
host_port=$(ccache_format_host_port "${ccache_ip}" "6379")
249+
export CCACHE_REMOTE_STORAGE="redis://${host_port}|connect-timeout=${CCACHE_REDIS_CONNECT_TIMEOUT}"
203250
display_alert "mDNS: discovered ccache" "$(ccache_mask_storage_url "${CCACHE_REMOTE_STORAGE}")" "info"
204251
return 0
205252
fi
@@ -211,12 +258,15 @@ function ccache_discover_remote_storage() {
211258
function ccache_get_redis_stats() {
212259
local ip="$1"
213260
local port="${2:-6379}"
261+
local password="$3"
214262
local stats=""
215263

216264
if command -v redis-cli &>/dev/null; then
265+
local auth_args=()
266+
[[ -n "${password}" ]] && auth_args+=(-a "${password}" --no-auth-warning)
217267
local keys mem
218-
keys=$(timeout 2 redis-cli -h "$ip" -p "$port" DBSIZE 2>/dev/null | grep -oE '[0-9]+' || true)
219-
mem=$(timeout 2 redis-cli -h "$ip" -p "$port" INFO memory 2>/dev/null | grep "used_memory_human" | cut -d: -f2 | tr -d '[:space:]' || true)
268+
keys=$(timeout 2 redis-cli -h "$ip" -p "$port" "${auth_args[@]}" DBSIZE 2>/dev/null | grep -oE '[0-9]+' || true)
269+
mem=$(timeout 2 redis-cli -h "$ip" -p "$port" "${auth_args[@]}" INFO memory 2>/dev/null | grep "used_memory_human" | cut -d: -f2 | tr -d '[:space:]' || true)
220270
if [[ -n "$keys" ]]; then
221271
stats="keys=${keys:-0}, mem=${mem:-?}"
222272
fi
@@ -242,10 +292,30 @@ function ccache_get_http_stats() {
242292
}
243293

244294
# Query remote storage stats based on URL scheme (redis:// or http://)
295+
# Parses userinfo (user:pass@) from Redis URLs to pass credentials to redis-cli
245296
function ccache_get_remote_stats() {
246297
local url="$1"
247-
if [[ "${url}" =~ ^redis://([^:/|]+):?([0-9]*) ]]; then
248-
ccache_get_redis_stats "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]:-6379}"
298+
if [[ "${url}" =~ ^redis:// ]]; then
299+
local password="" host="" port="6379"
300+
# Strip scheme and attributes
301+
local authority="${url#redis://}"
302+
authority="${authority%%|*}"
303+
# Extract password from userinfo (before last @)
304+
if [[ "${authority}" =~ ^(.+)@(.+)$ ]]; then
305+
local userinfo="${BASH_REMATCH[1]}"
306+
authority="${BASH_REMATCH[2]}"
307+
# password is after : in userinfo (user:pass or just :pass)
308+
[[ "${userinfo}" == *:* ]] && password="${userinfo#*:}"
309+
fi
310+
# Parse host:port (IPv6 in brackets or plain)
311+
if [[ "${authority}" =~ ^\[([^]]+)\]:?([0-9]*) ]]; then
312+
host="${BASH_REMATCH[1]}"
313+
[[ -n "${BASH_REMATCH[2]}" ]] && port="${BASH_REMATCH[2]}"
314+
elif [[ "${authority}" =~ ^([^:]+):?([0-9]*) ]]; then
315+
host="${BASH_REMATCH[1]}"
316+
[[ -n "${BASH_REMATCH[2]}" ]] && port="${BASH_REMATCH[2]}"
317+
fi
318+
[[ -n "${host}" ]] && ccache_get_redis_stats "${host}" "${port}" "${password}"
249319
elif [[ "${url}" =~ ^https?:// ]]; then
250320
# Strip ccache attributes after | for the URL
251321
ccache_get_http_stats "${url%%|*}"
@@ -283,9 +353,10 @@ function ccache_validate_storage_url() {
283353

284354
# This runs on the HOST just before Docker container is launched.
285355
# Resolves 'ccache.local' via mDNS (requires Avahi on server publishing this hostname
286-
# with: avahi-publish-address -R ccache.local <IP>) and passes the resolved IP
287-
# to Docker container via CCACHE_REMOTE_STORAGE environment variable.
288-
# mDNS resolution doesn't work inside Docker, so we must resolve on host.
356+
# Docker hook: resolve hostnames and handle loopback for container access.
357+
# mDNS/local DNS may not work inside Docker, so we resolve on host and
358+
# pass the mapping via --add-host. Loopback addresses are rewritten to
359+
# host.docker.internal.
289360
function host_pre_docker_launch__setup_remote_ccache() {
290361
if [[ -n "${CCACHE_REMOTE_STORAGE}" ]]; then
291362
ccache_validate_storage_url "${CCACHE_REMOTE_STORAGE}" || return 1
@@ -303,6 +374,37 @@ function host_pre_docker_launch__setup_remote_ccache() {
303374
fi
304375
fi
305376

377+
# Ensure hostname in CCACHE_REMOTE_STORAGE is resolvable inside Docker.
378+
# Docker containers may not have access to host mDNS/local DNS.
379+
if [[ -n "${CCACHE_REMOTE_STORAGE}" ]]; then
380+
local _host
381+
_host=$(ccache_extract_url_host "${CCACHE_REMOTE_STORAGE}")
382+
if [[ -n "${_host}" ]]; then
383+
# Loopback addresses: rewrite to host.docker.internal
384+
if [[ "${_host}" == "localhost" || "${_host}" == "127.0.0.1" || "${_host}" == "::1" ]]; then
385+
CCACHE_REMOTE_STORAGE="${CCACHE_REMOTE_STORAGE//localhost/host.docker.internal}"
386+
CCACHE_REMOTE_STORAGE="${CCACHE_REMOTE_STORAGE//127.0.0.1/host.docker.internal}"
387+
CCACHE_REMOTE_STORAGE="${CCACHE_REMOTE_STORAGE//\[::1\]/host.docker.internal}"
388+
DOCKER_EXTRA_ARGS+=("--add-host=host.docker.internal:host-gateway")
389+
display_alert "Rewriting loopback URL for Docker" "$(ccache_mask_storage_url "${CCACHE_REMOTE_STORAGE}")" "info"
390+
# Hostname (not IP): resolve on host and pass via --add-host
391+
elif [[ "${_host}" =~ [a-zA-Z] ]]; then
392+
local _resolved_ip="${CCACHE_REMOTE_HOST_IP:-}"
393+
# If not from discovery, resolve now; prefer IPv4 (Docker bridge often lacks IPv6)
394+
if [[ -z "${_resolved_ip}" || "${CCACHE_REMOTE_HOST}" != "${_host}" ]]; then
395+
_resolved_ip=$(getent ahostsv4 "${_host}" 2>/dev/null | awk '{print $1; exit}' || true)
396+
[[ -z "${_resolved_ip}" ]] && _resolved_ip=$(getent hosts "${_host}" 2>/dev/null | awk '{print $1; exit}' || true)
397+
fi
398+
if [[ -n "${_resolved_ip}" ]]; then
399+
DOCKER_EXTRA_ARGS+=("--add-host=${_host}:${_resolved_ip}")
400+
display_alert "Docker --add-host" "${_host}:${_resolved_ip}" "info"
401+
else
402+
display_alert "Cannot resolve hostname for Docker" "${_host}" "wrn"
403+
fi
404+
fi
405+
fi
406+
fi
407+
306408
# Pass all set CCACHE_* variables to Docker
307409
local var val
308410
for var in "${CCACHE_PASSTHROUGH_VARS[@]}"; do
@@ -333,8 +435,11 @@ function ccache_post_compilation__show_remote_stats() {
333435

334436
# This runs inside Docker (or native build) during configuration
335437
function extension_prepare_config__setup_remote_ccache() {
336-
# Enable ccache
438+
# Enable ccache with a consistent cache directory ($SRC/cache/ccache).
439+
# PRIVATE_CCACHE ensures the same CCACHE_DIR is used in native and Docker builds,
440+
# avoiding fragmented caches in /root/.cache/ccache vs $SRC/cache/ccache.
337441
declare -g USE_CCACHE=yes
442+
declare -g PRIVATE_CCACHE=yes
338443

339444
# If CCACHE_REMOTE_STORAGE was passed from host (via Docker env), it's already set
340445
if [[ -n "${CCACHE_REMOTE_STORAGE}" ]]; then

0 commit comments

Comments
 (0)