From 266b07b073b2144cc184c5d24ce6c7ef083b1044 Mon Sep 17 00:00:00 2001 From: Amit Kanfer Date: Thu, 5 Mar 2026 09:20:39 +0000 Subject: [PATCH 1/3] Fix health check failures when Docker proxy is configured, and VERSION unbound variable (#79) Two bugs fixed: 1. Health check curl commands inside containers did not bypass the Docker daemon proxy (http_proxy/https_proxy). When users have a system-wide Docker proxy configured, curl routes internal Docker network requests (e.g. http://elasticsearch:9200) through the proxy, causing the health check to fail even though Elasticsearch is running correctly. Adding --noproxy '*' ensures health checks always connect directly within the Docker network. Affected: ES health check, kibana_settings password setup curl, and Kibana health check. 2. On rolling-release distributions like Arch Linux, /etc/os-release does not define the VERSION variable (only VERSION_ID). Because the script runs with set -eu, referencing $VERSION caused an unbound variable error that crashed the error-log generation, hiding the real failure message. Fixed by using ${VERSION:-} as a safe default. Fixes #79 Co-Authored-By: Claude Sonnet 4.6 --- start-local.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/start-local.sh b/start-local.sh index 3e9c2bf..f0b787e 100755 --- a/start-local.sh +++ b/start-local.sh @@ -182,7 +182,7 @@ get_os_info() { # Most modern Linux distributions have this file . /etc/os-release echo "Distribution: $NAME" - echo "Version: $VERSION" + echo "Version: ${VERSION:-}" elif [ -f /etc/lsb-release ]; then # For older distributions using LSB (Linux Standard Base) . /etc/lsb-release @@ -897,7 +897,7 @@ EOM test: [ "CMD-SHELL", - "curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:9200", + "curl --noproxy '*' --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:9200", ] interval: 10s timeout: 10s @@ -919,7 +919,7 @@ if [ "$esonly" = "false" ]; then echo "Setup the kibana_system password"; start_time=$$(date +%s); timeout=60; - until curl -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do + until curl --noproxy '*' -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do if [ $$(($$(date +%s) - $$start_time)) -ge $$timeout ]; then echo "Error: Elasticsearch timeout"; exit 1; @@ -968,7 +968,7 @@ fi test: [ "CMD-SHELL", - "curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'", + "curl --noproxy '*' -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'", ] interval: 10s timeout: 10s From e507a64da503a7ee4c51575f19f1026de03149c1 Mon Sep 17 00:00:00 2001 From: Amit Kanfer Date: Thu, 5 Mar 2026 09:46:44 +0000 Subject: [PATCH 2/3] Add CI tests for proxy health check and VERSION unbound variable fixes - tests/get_os_info_test.sh: unit test verifying ${VERSION:-} does not crash under set -eu when VERSION is absent from /etc/os-release (Arch Linux). Includes a negative test confirming bare $VERSION would have crashed. - tests/docker/proxy.sh: integration test verifying ES and Kibana health checks succeed when Docker client proxy config injects HTTP_PROXY into containers. Configures ~/.docker/config.json to point at a non-existent proxy (127.0.0.1:19999), then runs start-local.sh and asserts both services return HTTP 200. Co-Authored-By: Claude Sonnet 4.6 --- tests/docker/proxy.sh | 79 +++++++++++++++++++++++++++++++++++++++ tests/get_os_info_test.sh | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 tests/docker/proxy.sh create mode 100644 tests/get_os_info_test.sh diff --git a/tests/docker/proxy.sh b/tests/docker/proxy.sh new file mode 100644 index 0000000..cf6f4ef --- /dev/null +++ b/tests/docker/proxy.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Regression test for https://github.com/elastic/start-local/issues/79 (Bug 1): +# When Docker client proxy config injects HTTP_PROXY into containers, curl health +# checks route internal Docker network requests through the proxy and fail. +# The fix adds --noproxy '*' to all health check curl commands. + +CURRENT_DIR=$(pwd) +DEFAULT_DIR="${CURRENT_DIR}/elastic-start-local" +ENV_PATH="${DEFAULT_DIR}/.env" +UNINSTALL_FILE="${DEFAULT_DIR}/uninstall.sh" +DOCKER_CONFIG_FILE="${HOME}/.docker/config.json" +DOCKER_CONFIG_BACKUP="" + +# include external scripts +source "${CURRENT_DIR}/tests/utility.sh" + +function set_up_before_script() { + # Back up existing Docker client config (it may contain auth credentials) + if [ -f "${DOCKER_CONFIG_FILE}" ]; then + DOCKER_CONFIG_BACKUP=$(mktemp) + cp "${DOCKER_CONFIG_FILE}" "${DOCKER_CONFIG_BACKUP}" + fi + + # Inject a proxy pointing to a port where nothing listens (127.0.0.1:19999). + # Docker propagates this as HTTP_PROXY/HTTPS_PROXY into every container. + # Without --noproxy '*', curl health checks would fail (connection refused). + # We preserve any existing config keys (e.g. auth) via jq merge. + mkdir -p "${HOME}/.docker" + local proxy_snippet='{"proxies":{"default":{"httpProxy":"http://127.0.0.1:19999","httpsProxy":"http://127.0.0.1:19999"}}}' + if [ -n "${DOCKER_CONFIG_BACKUP}" ] && command -v jq > /dev/null 2>&1; then + jq ". + ${proxy_snippet}" "${DOCKER_CONFIG_BACKUP}" > "${DOCKER_CONFIG_FILE}" + else + printf '%s\n' "${proxy_snippet}" > "${DOCKER_CONFIG_FILE}" + fi + + # shellcheck disable=SC2086 + sh "${CURRENT_DIR}/${SCRIPT_FILE}"${SCRIPT_EXTRA_ARGS} + # shellcheck disable=SC1090 + source "${ENV_PATH}" +} + +function tear_down_after_script() { + printf "yes\nno\n" | "${UNINSTALL_FILE}" + rm -rf "${DEFAULT_DIR}" + + # Restore the original Docker client config + if [ -n "${DOCKER_CONFIG_BACKUP}" ] && [ -f "${DOCKER_CONFIG_BACKUP}" ]; then + mv "${DOCKER_CONFIG_BACKUP}" "${DOCKER_CONFIG_FILE}" + else + rm -f "${DOCKER_CONFIG_FILE}" + fi +} + +function test_elasticsearch_accessible_with_proxy_configured() { + result=$(get_http_response_code "http://localhost:9200" "elastic" "${ES_LOCAL_PASSWORD}") + assert_equals "200" "${result}" +} + +function test_kibana_accessible_with_proxy_configured() { + result=$(get_http_response_code "http://localhost:5601") + assert_equals "200" "${result}" +} diff --git a/tests/get_os_info_test.sh b/tests/get_os_info_test.sh new file mode 100644 index 0000000..accf630 --- /dev/null +++ b/tests/get_os_info_test.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Regression test for https://github.com/elastic/start-local/issues/79 (Bug 2): +# On rolling-release distros like Arch Linux, /etc/os-release does not define VERSION. +# With `set -eu`, bare $VERSION causes an "unbound variable" crash. +# The fix uses ${VERSION:-} to default to an empty string. + +# Test that the fixed code pattern does not crash when VERSION is absent from os-release +function test_get_os_info_succeeds_when_VERSION_is_not_defined() { + tmpdir=$(mktemp -d) + cat > "${tmpdir}/os-release" << 'EOF' +NAME="Arch Linux" +ID=arch +PRETTY_NAME="Arch Linux" +HOME_URL="https://archlinux.org/" +EOF + + # Run the exact code path from get_os_info() under set -eu. + # ${VERSION:-} should safely default to "" when VERSION is not exported. + output=$(bash -euc " + . '${tmpdir}/os-release' + echo \"Distribution: \$NAME\" + echo \"Version: \${VERSION:-}\" + " 2>&1) + exit_code=$? + rm -rf "${tmpdir}" + + assert_equals "0" "${exit_code}" + assert_contains "Arch Linux" "${output}" +} + +# Confirm that the unfixed pattern ($VERSION without :-) would have crashed, +# making the above test meaningful. +function test_bare_VERSION_crashes_under_set_eu_when_not_defined() { + tmpdir=$(mktemp -d) + cat > "${tmpdir}/os-release" << 'EOF' +NAME="Arch Linux" +ID=arch +EOF + + bash -euc " + . '${tmpdir}/os-release' + echo \"\$VERSION\" + " > /dev/null 2>&1 + exit_code=$? + rm -rf "${tmpdir}" + + assert_not_equals "0" "${exit_code}" +} From 1bbe0230df118cf4566ee71512a08f0ec1930dbb Mon Sep 17 00:00:00 2001 From: Amit Kanfer Date: Thu, 5 Mar 2026 10:09:51 +0000 Subject: [PATCH 3/3] Fix quoting of --noproxy inside kibana_settings bash -c command The kibana_settings Docker Compose command uses bash -c '...' with single quotes. Using --noproxy '*' inside this context breaks the outer single-quoting (the ' in '*' closes the bash -c argument). Use --noproxy "*" (double quotes) instead, which is safe inside a single-quoted context and passes the literal * to curl. Co-Authored-By: Claude Sonnet 4.6 --- start-local.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start-local.sh b/start-local.sh index f0b787e..1b97014 100755 --- a/start-local.sh +++ b/start-local.sh @@ -919,7 +919,7 @@ if [ "$esonly" = "false" ]; then echo "Setup the kibana_system password"; start_time=$$(date +%s); timeout=60; - until curl --noproxy '*' -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do + until curl --noproxy "*" -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_LOCAL_PASSWORD}\"}" -H "Content-Type: application/json" | grep -q "^{}"; do if [ $$(($$(date +%s) - $$start_time)) -ge $$timeout ]; then echo "Error: Elasticsearch timeout"; exit 1;