#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # Pass host user identity so the container runs as the same UID/GID export HOST_UID="$(id -u)" export HOST_GID="$(id -g)" export HOST_USER="$(whoami)" # --- DEBUG=1 convenience flag ------------------------------------ # `DEBUG=1 bin/up` turns on full LLM debug logging (request/response # bodies) AND the per-step usage split (cache read/write tokens), and # routes the JSON log files into `logging/` at the project root. That # directory is mounted into the container at /app/logging via the # `.:/app` bind, so the files land on the host where you can # `tail -F logging/*.json` without `docker exec`. `logging/` is # gitignored. # # It only sets DEFAULTS -- any DISPATCH_DEBUG_* var you set explicitly # still wins, so fine-grained control (e.g. a custom verbosity or a # different log dir) is preserved. if [ -n "${DEBUG:-}" ] && [ "$DEBUG" != "0" ]; then : "${DISPATCH_DEBUG_LLM:=1}" : "${DISPATCH_DEBUG_USAGE:=1}" : "${DISPATCH_DEBUG_LLM_DIR:=/app/logging}" fi # Debug-logger pass-through. docker-compose only forwards env vars that are # (a) set in the parent shell AND (b) referenced in docker-compose.yml's # `environment:` block -- so without this `export` step the variables would # be invisible to the container even when the user prefixes the command with # DISPATCH_DEBUG_LLM=1. We re-export here (rather than relying on the shell's # inline `VAR=... cmd` syntax) so it works whether the user sets them inline, # in their shell rc, or via `.env`. # # All variables default to empty -- when unset, the logger short-circuits and # does nothing. export DISPATCH_DEBUG_LLM="${DISPATCH_DEBUG_LLM:-}" export DISPATCH_DEBUG_LLM_VERBOSITY="${DISPATCH_DEBUG_LLM_VERBOSITY:-}" export DISPATCH_DEBUG_USAGE="${DISPATCH_DEBUG_USAGE:-}" # Container-side log directory. Empty => the logger uses its built-in default # (/tmp/dispatch/llm-debug). DEBUG=1 sets it to /app/logging (see above). export DISPATCH_DEBUG_LLM_DIR="${DISPATCH_DEBUG_LLM_DIR:-}" # Pre-create the debug log directory owned by the host user. Without this, # docker auto-creates the bind-mount source as root on first start, and the # container's bun process (running as host UID) then gets EACCES on every log # write -- silent except for `[dispatch-debug] Failed to write ...: EACCES` # lines drowned in stderr. # # The host path depends on which container dir the logger targets: # - /app/ -> the project bind-mount, host path $PROJECT_DIR/ # (already host-owned; a plain mkdir is enough). # - otherwise -> the dedicated /tmp/dispatch/llm-debug volume; may have # been root-created by a prior run, so fix ownership. case "${DISPATCH_DEBUG_LLM_DIR}" in /app/*) HOST_LOG_DIR="$PROJECT_DIR/${DISPATCH_DEBUG_LLM_DIR#/app/}" mkdir -p "$HOST_LOG_DIR" 2>/dev/null || true ;; *) HOST_LOG_DIR=/tmp/dispatch/llm-debug mkdir -p "$HOST_LOG_DIR" 2>/dev/null || true if [ ! -O "$HOST_LOG_DIR" ]; then current_owner=$(stat -c '%U' "$HOST_LOG_DIR" 2>/dev/null || echo "unknown") echo "bin/up: $HOST_LOG_DIR is owned by '$current_owner', fixing ownership to '$HOST_USER'..." sudo chown -R "$HOST_UID:$HOST_GID" "$HOST_LOG_DIR" fi ;; esac if [ -n "${DISPATCH_DEBUG_LLM}" ]; then echo "bin/up: debug logging ON -> ${DISPATCH_DEBUG_LLM_DIR:-/tmp/dispatch/llm-debug} (host: $HOST_LOG_DIR)" fi # Start all services docker compose -f "$PROJECT_DIR/docker-compose.yml" up "$@"