From 5a3824414050430600dfc63be5d44fa794198fe2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 14:07:36 +0000 Subject: [PATCH 1/5] Initial plan From 84a0dd9ef2f27de3e8fa92b6c25900958a18022f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 14:14:32 +0000 Subject: [PATCH 2/5] Conditionally disable opentelemetry wrapper during init startup Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com> --- docker/init_scripts/init | 81 ++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/docker/init_scripts/init b/docker/init_scripts/init index 39ee0f2ac..e082c23c0 100755 --- a/docker/init_scripts/init +++ b/docker/init_scripts/init @@ -64,9 +64,17 @@ error_log() { # Commands to run initial startup tasks run_startup() { - if ! PYTHONPATH="/backend:${PYTHONPATH-}" opentelemetry-instrument \ - --service_name "${OTEL_SERVICE_NAME_PREFIX-}startup" \ - python3 /backend/startup.py; then + if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then + PYTHONPATH="/backend:${PYTHONPATH-}" python3 /backend/startup.py + elif command -v opentelemetry-instrument >/dev/null 2>&1; then + PYTHONPATH="/backend:${PYTHONPATH-}" opentelemetry-instrument \ + --service_name "${OTEL_SERVICE_NAME_PREFIX-}startup" \ + python3 /backend/startup.py + else + warn_log "opentelemetry-instrument not found, starting without OpenTelemetry instrumentation" + PYTHONPATH="/backend:${PYTHONPATH-}" python3 /backend/startup.py + fi + if [[ $? -ne 0 ]]; then error_log "Startup script failed, exiting" fi } @@ -107,23 +115,58 @@ start_bin_gunicorn() { export PYTHONUNBUFFERED=1 export PYTHONDONTWRITEBYTECODE=1 - opentelemetry-instrument \ - --service_name "${OTEL_SERVICE_NAME_PREFIX-}api" \ + if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then gunicorn \ - --bind=0.0.0.0:"${DEV_PORT:-5000}" \ - --bind=unix:/tmp/gunicorn.sock \ - --pid=/tmp/gunicorn.pid \ - --forwarded-allow-ips="*" \ - --worker-class uvicorn_worker.UvicornWorker \ - --workers "${WEB_SERVER_CONCURRENCY:-1}" \ - --timeout "${WEB_SERVER_TIMEOUT:-300}" \ - --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ - --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ - --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ - --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ - --error-logfile - \ - --log-config "${gunicorn_log_config}" \ - main:app & + --bind=0.0.0.0:"${DEV_PORT:-5000}" \ + --bind=unix:/tmp/gunicorn.sock \ + --pid=/tmp/gunicorn.pid \ + --forwarded-allow-ips="*" \ + --worker-class uvicorn_worker.UvicornWorker \ + --workers "${WEB_SERVER_CONCURRENCY:-1}" \ + --timeout "${WEB_SERVER_TIMEOUT:-300}" \ + --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ + --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ + --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ + --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ + --error-logfile - \ + --log-config "${gunicorn_log_config}" \ + main:app & + elif command -v opentelemetry-instrument >/dev/null 2>&1; then + opentelemetry-instrument \ + --service_name "${OTEL_SERVICE_NAME_PREFIX-}api" \ + gunicorn \ + --bind=0.0.0.0:"${DEV_PORT:-5000}" \ + --bind=unix:/tmp/gunicorn.sock \ + --pid=/tmp/gunicorn.pid \ + --forwarded-allow-ips="*" \ + --worker-class uvicorn_worker.UvicornWorker \ + --workers "${WEB_SERVER_CONCURRENCY:-1}" \ + --timeout "${WEB_SERVER_TIMEOUT:-300}" \ + --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ + --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ + --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ + --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ + --error-logfile - \ + --log-config "${gunicorn_log_config}" \ + main:app & + else + warn_log "opentelemetry-instrument not found, starting gunicorn without OpenTelemetry instrumentation" + gunicorn \ + --bind=0.0.0.0:"${DEV_PORT:-5000}" \ + --bind=unix:/tmp/gunicorn.sock \ + --pid=/tmp/gunicorn.pid \ + --forwarded-allow-ips="*" \ + --worker-class uvicorn_worker.UvicornWorker \ + --workers "${WEB_SERVER_CONCURRENCY:-1}" \ + --timeout "${WEB_SERVER_TIMEOUT:-300}" \ + --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ + --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ + --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ + --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ + --error-logfile - \ + --log-config "${gunicorn_log_config}" \ + main:app & + fi } # Commands to start nginx (handling PID creation internally) From f434fde7724e5c525e8765f9300390679cefe48d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 18:21:40 +0000 Subject: [PATCH 3/5] Refactor OTEL wrapping into shared helpers Collapse the duplicated OTEL_SDK_DISABLED / opentelemetry-instrument branches in run_startup, start_bin_gunicorn, start_bin_watcher, and start_bin_sync_watcher into two small helpers: - otel_prefix: emits the wrapper as NUL-delimited argv tokens (for direct process invocation). - otel_prefix_str: emits the wrapper as a shell-string prefix (for embedding inside `watchfiles --target-type command`). Each call site becomes a single command instead of a 2- or 3-way branch with a fully duplicated command body. As a side effect, the watcher functions now also gain the `command -v opentelemetry-instrument` fallback that the gunicorn/startup paths added. --- docker/init_scripts/init | 138 +++++++++++++++------------------------ 1 file changed, 51 insertions(+), 87 deletions(-) diff --git a/docker/init_scripts/init b/docker/init_scripts/init index e082c23c0..d2d871ce1 100755 --- a/docker/init_scripts/init +++ b/docker/init_scripts/init @@ -62,19 +62,34 @@ error_log() { exit 1 } +# Emit the opentelemetry-instrument wrapper as NUL-delimited argv tokens, or +# nothing if OTEL is disabled or the wrapper binary is missing. +otel_prefix() { + local service="$1" + if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then return 0; fi + if ! command -v opentelemetry-instrument >/dev/null 2>&1; then + # stderr so it doesn't pollute the captured argv tokens + warn_log "opentelemetry-instrument not found, starting ${service} without OpenTelemetry instrumentation" >&2 + return 0 + fi + printf '%s\0' opentelemetry-instrument --service_name "${OTEL_SERVICE_NAME_PREFIX-}${service}" +} + +# Emit the opentelemetry-instrument wrapper as a shell-string prefix (with +# trailing space), for embedding inside `watchfiles --target-type command`. +otel_prefix_str() { + local service="$1" + if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]] || ! command -v opentelemetry-instrument >/dev/null 2>&1; then + return 0 + fi + printf "opentelemetry-instrument --service_name '%s%s' " "${OTEL_SERVICE_NAME_PREFIX-}" "${service}" +} + # Commands to run initial startup tasks run_startup() { - if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then - PYTHONPATH="/backend:${PYTHONPATH-}" python3 /backend/startup.py - elif command -v opentelemetry-instrument >/dev/null 2>&1; then - PYTHONPATH="/backend:${PYTHONPATH-}" opentelemetry-instrument \ - --service_name "${OTEL_SERVICE_NAME_PREFIX-}startup" \ - python3 /backend/startup.py - else - warn_log "opentelemetry-instrument not found, starting without OpenTelemetry instrumentation" - PYTHONPATH="/backend:${PYTHONPATH-}" python3 /backend/startup.py - fi - if [[ $? -ne 0 ]]; then + local -a wrap=() + mapfile -d '' wrap < <(otel_prefix startup) + if ! PYTHONPATH="/backend:${PYTHONPATH-}" "${wrap[@]}" python3 /backend/startup.py; then error_log "Startup script failed, exiting" fi } @@ -115,58 +130,23 @@ start_bin_gunicorn() { export PYTHONUNBUFFERED=1 export PYTHONDONTWRITEBYTECODE=1 - if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then - gunicorn \ - --bind=0.0.0.0:"${DEV_PORT:-5000}" \ - --bind=unix:/tmp/gunicorn.sock \ - --pid=/tmp/gunicorn.pid \ - --forwarded-allow-ips="*" \ - --worker-class uvicorn_worker.UvicornWorker \ - --workers "${WEB_SERVER_CONCURRENCY:-1}" \ - --timeout "${WEB_SERVER_TIMEOUT:-300}" \ - --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ - --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ - --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ - --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ - --error-logfile - \ - --log-config "${gunicorn_log_config}" \ - main:app & - elif command -v opentelemetry-instrument >/dev/null 2>&1; then - opentelemetry-instrument \ - --service_name "${OTEL_SERVICE_NAME_PREFIX-}api" \ - gunicorn \ - --bind=0.0.0.0:"${DEV_PORT:-5000}" \ - --bind=unix:/tmp/gunicorn.sock \ - --pid=/tmp/gunicorn.pid \ - --forwarded-allow-ips="*" \ - --worker-class uvicorn_worker.UvicornWorker \ - --workers "${WEB_SERVER_CONCURRENCY:-1}" \ - --timeout "${WEB_SERVER_TIMEOUT:-300}" \ - --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ - --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ - --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ - --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ - --error-logfile - \ - --log-config "${gunicorn_log_config}" \ - main:app & - else - warn_log "opentelemetry-instrument not found, starting gunicorn without OpenTelemetry instrumentation" - gunicorn \ - --bind=0.0.0.0:"${DEV_PORT:-5000}" \ - --bind=unix:/tmp/gunicorn.sock \ - --pid=/tmp/gunicorn.pid \ - --forwarded-allow-ips="*" \ - --worker-class uvicorn_worker.UvicornWorker \ - --workers "${WEB_SERVER_CONCURRENCY:-1}" \ - --timeout "${WEB_SERVER_TIMEOUT:-300}" \ - --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ - --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ - --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ - --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ - --error-logfile - \ - --log-config "${gunicorn_log_config}" \ - main:app & - fi + local -a wrap=() + mapfile -d '' wrap < <(otel_prefix api) + "${wrap[@]}" gunicorn \ + --bind=0.0.0.0:"${DEV_PORT:-5000}" \ + --bind=unix:/tmp/gunicorn.sock \ + --pid=/tmp/gunicorn.pid \ + --forwarded-allow-ips="*" \ + --worker-class uvicorn_worker.UvicornWorker \ + --workers "${WEB_SERVER_CONCURRENCY:-1}" \ + --timeout "${WEB_SERVER_TIMEOUT:-300}" \ + --keep-alive "${WEB_SERVER_KEEPALIVE:-2}" \ + --max-requests "${WEB_SERVER_MAX_REQUESTS:-1000}" \ + --max-requests-jitter "${WEB_SERVER_MAX_REQUESTS_JITTER:-100}" \ + --worker-connections "${WEB_SERVER_WORKER_CONNECTIONS:-1000}" \ + --error-logfile - \ + --log-config "${gunicorn_log_config}" \ + main:app & } # Commands to start nginx (handling PID creation internally) @@ -272,19 +252,10 @@ start_bin_rq_worker() { start_bin_watcher() { info_log "Starting watcher" - if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then - # Skip opentelemetry-instrument when OTEL is disabled to ensure - # WATCHFILES_CHANGES env var is properly passed to watcher.py - watchfiles \ - --target-type command \ - "python3 watcher.py" \ - /romm/library & - else - watchfiles \ - --target-type command \ - "opentelemetry-instrument --service_name '${OTEL_SERVICE_NAME_PREFIX-}watcher' python3 watcher.py" \ - /romm/library & - fi + watchfiles \ + --target-type command \ + "$(otel_prefix_str watcher)python3 watcher.py" \ + /romm/library & WATCHER_PID=$! echo "${WATCHER_PID}" >/tmp/watcher.pid } @@ -293,17 +264,10 @@ start_bin_sync_watcher() { info_log "Starting sync folder watcher" sync_base_path="${ROMM_BASE_PATH:-/romm}/sync" mkdir -p "${sync_base_path}" - if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then - watchfiles \ - --target-type command \ - "python3 sync_watcher.py" \ - "${sync_base_path}" & - else - watchfiles \ - --target-type command \ - "opentelemetry-instrument --service_name '${OTEL_SERVICE_NAME_PREFIX-}sync_watcher' python3 sync_watcher.py" \ - "${sync_base_path}" & - fi + watchfiles \ + --target-type command \ + "$(otel_prefix_str sync_watcher)python3 sync_watcher.py" \ + "${sync_base_path}" & SYNC_WATCHER_PID=$! echo "${SYNC_WATCHER_PID}" >/tmp/sync_watcher.pid } From a4f1f516bc42d2b2eb17c3890d67d97b63857611 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sun, 24 May 2026 14:57:52 -0400 Subject: [PATCH 4/5] Simplify OTEL wrapper helper and fix shell quoting Collapse `otel_prefix` and `otel_prefix_str` into a single nameref-based helper. Watchfiles call sites embed the array as a shell-quoted prefix via `${wrap[*]@Q}`, which also fixes a quoting bug where an `OTEL_SERVICE_NAME_PREFIX` containing a single quote would produce an invalid command string and break the watcher. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/init_scripts/init | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/docker/init_scripts/init b/docker/init_scripts/init index d2d871ce1..c29493afd 100755 --- a/docker/init_scripts/init +++ b/docker/init_scripts/init @@ -62,33 +62,27 @@ error_log() { exit 1 } -# Emit the opentelemetry-instrument wrapper as NUL-delimited argv tokens, or -# nothing if OTEL is disabled or the wrapper binary is missing. +# Populate a caller-provided array with the opentelemetry-instrument wrapper +# argv tokens for service "$2", or leave it empty if OTEL is disabled or the +# wrapper binary is missing. Use "${arr[@]}" to exec directly, or +# "${arr[*]@Q}" to embed as a shell-quoted prefix string. otel_prefix() { - local service="$1" + local -n out_arr="$1" + # shellcheck disable=SC2034 # nameref binds out_arr to caller variable + out_arr=() if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then return 0; fi if ! command -v opentelemetry-instrument >/dev/null 2>&1; then - # stderr so it doesn't pollute the captured argv tokens - warn_log "opentelemetry-instrument not found, starting ${service} without OpenTelemetry instrumentation" >&2 + warn_log "opentelemetry-instrument not found, starting $2 without OpenTelemetry instrumentation" return 0 fi - printf '%s\0' opentelemetry-instrument --service_name "${OTEL_SERVICE_NAME_PREFIX-}${service}" -} - -# Emit the opentelemetry-instrument wrapper as a shell-string prefix (with -# trailing space), for embedding inside `watchfiles --target-type command`. -otel_prefix_str() { - local service="$1" - if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]] || ! command -v opentelemetry-instrument >/dev/null 2>&1; then - return 0 - fi - printf "opentelemetry-instrument --service_name '%s%s' " "${OTEL_SERVICE_NAME_PREFIX-}" "${service}" + # shellcheck disable=SC2034 # nameref binds out_arr to caller variable + out_arr=(opentelemetry-instrument --service_name "${OTEL_SERVICE_NAME_PREFIX-}$2") } # Commands to run initial startup tasks run_startup() { local -a wrap=() - mapfile -d '' wrap < <(otel_prefix startup) + otel_prefix wrap startup if ! PYTHONPATH="/backend:${PYTHONPATH-}" "${wrap[@]}" python3 /backend/startup.py; then error_log "Startup script failed, exiting" fi @@ -131,7 +125,7 @@ start_bin_gunicorn() { export PYTHONDONTWRITEBYTECODE=1 local -a wrap=() - mapfile -d '' wrap < <(otel_prefix api) + otel_prefix wrap api "${wrap[@]}" gunicorn \ --bind=0.0.0.0:"${DEV_PORT:-5000}" \ --bind=unix:/tmp/gunicorn.sock \ @@ -252,9 +246,11 @@ start_bin_rq_worker() { start_bin_watcher() { info_log "Starting watcher" + local -a wrap=() + otel_prefix wrap watcher watchfiles \ --target-type command \ - "$(otel_prefix_str watcher)python3 watcher.py" \ + "${wrap[*]@Q} python3 watcher.py" \ /romm/library & WATCHER_PID=$! echo "${WATCHER_PID}" >/tmp/watcher.pid @@ -264,9 +260,11 @@ start_bin_sync_watcher() { info_log "Starting sync folder watcher" sync_base_path="${ROMM_BASE_PATH:-/romm}/sync" mkdir -p "${sync_base_path}" + local -a wrap=() + otel_prefix wrap sync_watcher watchfiles \ --target-type command \ - "$(otel_prefix_str sync_watcher)python3 sync_watcher.py" \ + "${wrap[*]@Q} python3 sync_watcher.py" \ "${sync_base_path}" & SYNC_WATCHER_PID=$! echo "${SYNC_WATCHER_PID}" >/tmp/sync_watcher.pid From 354207d683afc0eb15cd715d38d887512eeb4452 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sun, 24 May 2026 15:17:42 -0400 Subject: [PATCH 5/5] Drop redundant out_arr reset in otel_prefix All callers declare a fresh `local -a wrap=()` before invoking, so the in-function reset is unnecessary. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/init_scripts/init | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/init_scripts/init b/docker/init_scripts/init index c29493afd..e94fd05a7 100755 --- a/docker/init_scripts/init +++ b/docker/init_scripts/init @@ -68,8 +68,6 @@ error_log() { # "${arr[*]@Q}" to embed as a shell-quoted prefix string. otel_prefix() { local -n out_arr="$1" - # shellcheck disable=SC2034 # nameref binds out_arr to caller variable - out_arr=() if [[ ${OTEL_SDK_DISABLED:-false} == "true" ]]; then return 0; fi if ! command -v opentelemetry-instrument >/dev/null 2>&1; then warn_log "opentelemetry-instrument not found, starting $2 without OpenTelemetry instrumentation"