From 19f10e33e6a915ada3c23c09ba3b00656f96b8b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 20:01:47 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20[nomad-prep]=20P6=20=E2=80=94=20external?= =?UTF-8?q?ize=20host=20paths=20in=20docker-compose=20via=20env=20vars=20(?= =?UTF-8?q?#795)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded host-side bind-mount paths with env vars so Nomad jobspecs can reuse the same variables at cutover: - CLAUDE_BIN_DIR: path to claude CLI binary (resolved at init time) - CLAUDE_CONFIG_FILE: path to .claude.json (default ${HOME}/.claude.json) - CLAUDE_DIR: path to .claude directory (default ${HOME}/.claude) - AGENT_SSH_DIR: path to SSH keys (default ${HOME}/.ssh) - SOPS_AGE_DIR: path to SOPS age keys (default ${HOME}/.config/sops/age) generators.sh now writes CLAUDE_BIN_DIR to .env instead of sed-replacing CLAUDE_BIN_PLACEHOLDER in docker-compose.yml. Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 10 ++++++++++ docker-compose.yml | 28 +++++++++++++-------------- lib/generators.sh | 48 +++++++++++++++++++++++++++------------------- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/.env.example b/.env.example index 1fede25..7e76ec2 100644 --- a/.env.example +++ b/.env.example @@ -109,6 +109,16 @@ ANTHROPIC_BASE_URL= # [CONFIG] e.g. http://host.docker.in # ── Tuning ──────────────────────────────────────────────────────────────── CLAUDE_TIMEOUT=7200 # [CONFIG] max seconds per Claude invocation +# ── Host paths (Nomad-portable) ──────────────────────────────────────────── +# These env vars externalize host-side bind-mount paths from docker-compose.yml. +# At cutover, Nomad jobspecs reference the same vars — no path translation. +# Defaults point at current paths so an empty .env override still works. +CLAUDE_BIN_DIR=/usr/local/bin/claude # [CONFIG] host path to claude CLI binary (resolved by `disinto init`) +CLAUDE_CONFIG_FILE=${HOME}/.claude.json # [CONFIG] host path to claude config JSON file +CLAUDE_DIR=${HOME}/.claude # [CONFIG] host path to .claude directory (reproduce/edge) +AGENT_SSH_DIR=${HOME}/.ssh # [CONFIG] host path to SSH keys directory +SOPS_AGE_DIR=${HOME}/.config/sops/age # [CONFIG] host path to SOPS age key directory + # ── Claude Code shared OAuth state ───────────────────────────────────────── # Shared directory used by every factory container so Claude Code's internal # proper-lockfile-based OAuth refresh lock works across containers. Both diff --git a/docker-compose.yml b/docker-compose.yml index c8c34ab..ba6a1fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,10 +14,10 @@ services: - agent-data:/home/agent/data - project-repos:/home/agent/repos - ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - ${HOME}/.claude.json:/home/agent/.claude.json:ro - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - - ${HOME}/.ssh:/home/agent/.ssh:ro - - ${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro + - ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro + - ${AGENT_SSH_DIR:-${HOME}/.ssh}:/home/agent/.ssh:ro + - ${SOPS_AGE_DIR:-${HOME}/.config/sops/age}:/home/agent/.config/sops/age:ro - woodpecker-data:/woodpecker-data:ro environment: - FORGE_URL=http://forgejo:3000 @@ -76,10 +76,10 @@ services: - agent-data:/home/agent/data - project-repos:/home/agent/repos - ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - ${HOME}/.claude.json:/home/agent/.claude.json:ro - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - - ${HOME}/.ssh:/home/agent/.ssh:ro - - ${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro + - ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro + - ${AGENT_SSH_DIR:-${HOME}/.ssh}:/home/agent/.ssh:ro + - ${SOPS_AGE_DIR:-${HOME}/.config/sops/age}:/home/agent/.config/sops/age:ro - woodpecker-data:/woodpecker-data:ro environment: - FORGE_URL=http://forgejo:3000 @@ -134,9 +134,9 @@ services: - /var/run/docker.sock:/var/run/docker.sock - agent-data:/home/agent/data - project-repos:/home/agent/repos - - ${HOME}/.claude:/home/agent/.claude - - /usr/local/bin/claude:/usr/local/bin/claude:ro - - ${HOME}/.ssh:/home/agent/.ssh:ro + - ${CLAUDE_DIR:-${HOME}/.claude}:/home/agent/.claude + - ${CLAUDE_BIN_DIR:-/usr/local/bin/claude}:/usr/local/bin/claude:ro + - ${AGENT_SSH_DIR:-${HOME}/.ssh}:/home/agent/.ssh:ro env_file: - .env @@ -150,9 +150,9 @@ services: - apparmor=unconfined volumes: - /var/run/docker.sock:/var/run/docker.sock - - /usr/local/bin/claude:/usr/local/bin/claude:ro - - ${HOME}/.claude.json:/root/.claude.json:ro - - ${HOME}/.claude:/root/.claude:ro + - ${CLAUDE_BIN_DIR:-/usr/local/bin/claude}:/usr/local/bin/claude:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/root/.claude.json:ro + - ${CLAUDE_DIR:-${HOME}/.claude}:/root/.claude:ro - disinto-logs:/opt/disinto-logs environment: - FORGE_SUPERVISOR_TOKEN=${FORGE_SUPERVISOR_TOKEN:-} diff --git a/lib/generators.sh b/lib/generators.sh index c32a543..6cfe832 100644 --- a/lib/generators.sh +++ b/lib/generators.sh @@ -109,9 +109,9 @@ _generate_local_model_services() { - agents-${service_name}-data:/home/agent/data - project-repos:/home/agent/repos - \${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:\${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - \${HOME}/.claude.json:/home/agent/.claude.json:ro - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - - \${HOME}/.ssh:/home/agent/.ssh:ro + - \${CLAUDE_CONFIG_FILE:-\${HOME}/.claude.json}:/home/agent/.claude.json:ro + - \${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro + - \${AGENT_SSH_DIR:-\${HOME}/.ssh}:/home/agent/.ssh:ro environment: FORGE_URL: http://forgejo:3000 FORGE_REPO: ${FORGE_REPO:-disinto-admin/disinto} @@ -339,10 +339,10 @@ services: - agent-data:/home/agent/data - project-repos:/home/agent/repos - ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - ${HOME}/.claude.json:/home/agent/.claude.json:ro - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - - ${HOME}/.ssh:/home/agent/.ssh:ro - - ${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro + - ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro + - ${AGENT_SSH_DIR:-${HOME}/.ssh}:/home/agent/.ssh:ro + - ${SOPS_AGE_DIR:-${HOME}/.config/sops/age}:/home/agent/.config/sops/age:ro - woodpecker-data:/woodpecker-data:ro - ./projects:/home/agent/disinto/projects:ro - ./.env:/home/agent/disinto/.env:ro @@ -414,10 +414,10 @@ COMPOSEEOF - agent-data:/home/agent/data - project-repos:/home/agent/repos - ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - ${HOME}/.claude.json:/home/agent/.claude.json:ro - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro - - ${HOME}/.ssh:/home/agent/.ssh:ro - - ${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro + - ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro + - ${AGENT_SSH_DIR:-${HOME}/.ssh}:/home/agent/.ssh:ro + - ${SOPS_AGE_DIR:-${HOME}/.config/sops/age}:/home/agent/.config/sops/age:ro - woodpecker-data:/woodpecker-data:ro environment: FORGE_URL: http://forgejo:3000 @@ -516,7 +516,7 @@ LLAMAEOF - /var/run/docker.sock:/var/run/docker.sock - ./secrets/tunnel_key:/run/secrets/tunnel_key:ro - ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared} - - ${HOME}/.claude.json:/home/agent/.claude.json:ro + - ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:2019/config/"] interval: 30s @@ -586,7 +586,7 @@ LLAMAEOF memswap_limit: 512m volumes: # Mount claude binary from host (same as agents) - - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro + - ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro # Throwaway named volume for chat config (isolated from host ~/.claude) - chat-config:/var/chat/config # Chat history persistence: per-user NDJSON files on bind-mounted host volume @@ -649,20 +649,28 @@ COMPOSEEOF fi # Append local-model agent services if any are configured - # (must run before CLAUDE_BIN_PLACEHOLDER substitution so the placeholder - # in local-model services is also resolved) _generate_local_model_services "$compose_file" - # Patch the Claude CLI binary path — resolve from host PATH at init time. + # Resolve the Claude CLI binary path and persist as CLAUDE_BIN_DIR in .env. + # docker-compose.yml references ${CLAUDE_BIN_DIR} so the value must be set. local claude_bin claude_bin="$(command -v claude 2>/dev/null || true)" if [ -n "$claude_bin" ]; then - # Resolve symlinks to get the real binary path claude_bin="$(readlink -f "$claude_bin")" - sed -i "s|CLAUDE_BIN_PLACEHOLDER|${claude_bin}|g" "$compose_file" else - echo "Warning: claude CLI not found in PATH — update docker-compose.yml volumes manually" >&2 - sed -i "s|CLAUDE_BIN_PLACEHOLDER|/usr/local/bin/claude|g" "$compose_file" + echo "Warning: claude CLI not found in PATH — set CLAUDE_BIN_DIR in .env manually" >&2 + claude_bin="/usr/local/bin/claude" + fi + # Persist CLAUDE_BIN_DIR into .env so docker-compose can resolve it. + local env_file="${FACTORY_ROOT}/.env" + if [ -f "$env_file" ]; then + if grep -q "^CLAUDE_BIN_DIR=" "$env_file" 2>/dev/null; then + sed -i "s|^CLAUDE_BIN_DIR=.*|CLAUDE_BIN_DIR=${claude_bin}|" "$env_file" + else + printf 'CLAUDE_BIN_DIR=%s\n' "$claude_bin" >> "$env_file" + fi + else + printf 'CLAUDE_BIN_DIR=%s\n' "$claude_bin" > "$env_file" fi # In build mode, replace image: with build: for locally-built images