Commit Graph
11 Commits
Author SHA1 Message Date
ClaudeandClaude Fable 5 e518c669e4 chore(docker): enable files_mcp service in container image
- Dockerfile: COPY files_mcp package, mkdir -p /app/data/files at build
  time, set ENV FILES_MCP_ROOT=/app/data/files as the default sandbox
  root (overridable at runtime).
- docker-compose.yml: surface FILES_MCP_ROOT in the env block with the
  same default, so operators can override it via .env or -e.

FILES_MCP_ROOT must exist at process start (path_guard._init_root
checks is_dir()), so the directory is created during build rather than
deferred to entrypoint — fail-fast at image build beats a runtime
RuntimeError on every container start.

Default lives under /app/data so it rides the existing data volume
(./data:/app/data) and persists across container restarts alongside
connections.json / pending_jobs.json / logs/.

Verified locally: docker compose config --quiet passes; FILES_MCP_ROOT
resolves to /app/data/files in the rendered config. Full image build
not run (docker daemon unavailable in this environment); please run
`docker compose up -d --build` to confirm in your environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-30 19:34:06 +08:00
Claude 9e7b425f09 update: container name in docker-compose.yml 2026-06-26 15:30:38 +08:00
Claude 0cb7605afa fix(deploy): align JAVA_HOME default to JDK 17 (match Dockerfile)
The Dockerfile installs openjdk-17-jre-headless and sets
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64, but the entrypoint
default was still /usr/lib/jvm/java-11-openjdk-amd64. The
validation caught the mismatch at container start:

  [entrypoint] FATAL: JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
  but /usr/lib/jvm/java-11-openjdk-amd64/bin/java is missing or
  not executable

That's exactly the failure mode the validation is designed to catch
- the deployment config got out of sync. Aligning all four files
(Dockerfile, entrypoint, .env.example, docker-compose.yml) to
java-17-openjdk-amd64.

No app code touched. 165/165 still pass.
2026-06-25 17:27:27 +08:00
Claude 1c9e4a321d feat: env-configurable spark-submit binary name
Hardcoding 'spark-submit' as cmd[0] in build_spark_submit_command
breaks for hosts where:
  - both Spark 1.x and 2.x/3.x are installed and 'spark-submit' resolves
    to the wrong one (use 'spark2-submit' or 'spark3-submit' explicitly)
  - the user wants to launch via the PySpark entrypoint ('pyspark')
  - a custom wrapper script sits on PATH (e.g. a credentials-injecting
    'spark-submit-wrapper')

New env var SPARK_EXECUTOR_SPARK_SUBMIT_BIN. Default is 'spark-submit'
(preserves the current behavior for everyone). Override in .env /
docker-compose.yml to change.

common/config.py:
  - new Settings.spark_submit_bin field
  - env-var resolution in from_env() with default 'spark-submit'
  - included in reload() so tests work

spark_executor/core/spark_submit.py:
  - cmd[0] reads settings.spark_submit_bin (was hardcoded 'spark-submit')

.env.example: new section with the override and example values.
docker-compose.yml: forwards the var with the standard 'spark-submit'
default.

Tests: 2 new (settings.spark_submit_bin='spark2-submit', ='pyspark')
plus existing tests updated to use the settings-restore fixture so
mutations don't leak between tests.

165/163 still pass.
2026-06-25 16:46:59 +08:00
Claude cb909f7fea fix(deploy): switch to JDK 11 + Spark 3.1.2, defensive shebang, validate entrypoint
Three fixes requested:

1. Spark 4.1.2 -> Spark 3.1.2 (Hadoop 2.7 prebuilt). Compatible with the
   JDK 11 build below and a more conservative choice for production.

2. JDK 17 -> JDK 11. openjdk-11-jre-headless package; JAVA_HOME points
   at /usr/lib/jvm/java-11-openjdk-amd64.

3. /usr/bin/env 'bach' no such file: defensive shebang fix. The
   downloaded spark-3.1.2-bin-hadoop2.7.tgz happens to have a clean
   shebang, but older 4.x distributions (and any future typo in a
   release) would break the same way we just saw. The Dockerfile now
   runs:
       find /opt/spark/bin -type f -exec sed -i '1s|^.*$|#!/usr/bin/env bash|' {} +
   which rewrites the first line of every bin/* script to a known-good
   shebang. Idempotent, defensive, costs nothing.

4. docker-entrypoint.sh: simplified and made validation explicit.
   Old version used an awk/sed pipeline to strip /usr/lib/jvm/ and
   /opt/spark/bin from the existing PATH before prepending the new
   values. That had a subtle bug: if the new JAVA_HOME was itself
   under /usr/lib/jvm/ (e.g. /usr/lib/jvm/java-11-openjdk-amd64), the
   strip would remove the new path too. New version just prepends the
   resolved paths and leaves the old PATH alone. The new paths win
   because they come first.

5. docker-entrypoint.sh: now validates the resolved paths BEFORE
   exporting them. If JAVA_HOME/bin/java or SPARK_HOME/bin/spark-submit
   are missing, the container fails fast with a clear hint instead of
   letting a job submission die with an opaque 'no such file'. Also
   logs the effective 'java' and 'spark-submit' paths (and java
   version) to stderr at every start, so docker logs make the
   resolution visible.

6. .env.example + docker-compose.yml: default JAVA_HOME updated to
   /usr/lib/jvm/java-11-openjdk-amd64. Spark client 3.1.2 (hadoop2.7)
   noted in the comment as the working combo.

163/146 still pass (no code changes to the app; Dockerfile + entrypoint
+ docs only). The new entrypoint was smoke-tested locally: validation
fires as expected (the local dev box has no JDK 11, which is exactly
the kind of misconfig the validation now catches at container start).
2026-06-25 16:43:40 +08:00
Claude 5970fadcd2 feat: env-configurable JAVA_HOME / SPARK_HOME via entrypoint
Three problems with the prior Dockerfile:

  1. JAVA_HOME and SPARK_HOME were hardcoded at build time (ENV ...),
     so docker-compose / .env overrides had no effect.
  2. PATH was constructed at build time using those hardcoded values,
     so even if you could override the env vars, PATH would still
     reference the old literal paths (e.g. /usr/lib/jvm/java-17-openjdk-amd64/bin
     hardcoded into PATH at image build).
  3. There was no .env.example entry for either, so operators had no
     template to follow.

Fix:
  - docker-entrypoint.sh: re-derives PATH from the (possibly overridden)
    JAVA_HOME and SPARK_HOME at every container start. Strips any stale
    JDK/Spark bin dirs from PATH first so a restart with a new override
    actually changes which  /  resolve.
  - Dockerfile: COPY + ENTRYPOINT [entrypoint.sh], CMD [gunicorn main:app].
    The existing ENV JAVA_HOME and ENV SPARK_HOME stay as the defaults
    so the image works out of the box; users override via .env.
  - .env.example: new 'Java + Spark install paths' section, default
    values match the Dockerfile, comments explain the entrypoint
    re-derivation.
  - docker-compose.yml: forwards JAVA_HOME and SPARK_HOME with
    container-side defaults matching the Dockerfile.

Verified:
  - 116/116 tests still pass
  - Direct entrypoint run with JAVA_HOME=/fake/jdk shows PATH rebuilt as
    /app/.venv/bin:/fake/jdk/bin:/fake/spark/bin:... (override took effect)

Note: uses awk instead of 'paste -sd:' for portability across macOS
(BSD paste doesn't support -s) and Linux (GNU does).
2026-06-25 11:30:30 +08:00
Claude 13e39b39f3 refactor: env-configurable loguru file paths
common/logging.py used hardcoded 'data/logs/{debug,info}/' paths,
which forced logs into the same dir as connections.json. Operators
couldn't put logs on a dedicated volume or a different disk for
retention policy reasons.

Now the base log dir comes from settings.log_dir (env var
SPARK_EXECUTOR_LOG_DIR, default <data_dir>/logs). Subdirs debug/ and
info/ are auto-created. Same behavior in dev (./data/logs/), but in
prod you can do:

  SPARK_EXECUTOR_LOG_DIR=/var/log/spark-executor   # dedicated volume
  SPARK_EXECUTOR_LOG_DIR=/mnt/slow-storage/logs   # cold storage for old logs

Other changes:
  - .env.example: new SPARK_EXECUTOR_LOG_DIR entry, split into its own
    'Loguru file sinks' section
  - docker-compose.yml: forwards SPARK_EXECUTOR_LOG_DIR with the
    container-side default /app/data/logs (the existing ./data:/app/data
    volume mount already covers this)
  - common/config.py: Settings gets a new log_dir field, defaults
    derived from data_dir; reload() also resets it

116/116 still pass. Live smoke verified: with
SPARK_EXECUTOR_LOG_DIR=/tmp/spark-logs, both
  /tmp/spark-logs/info/2026-06-25.log
  /tmp/spark-logs/debug/2026-06-25.log
are created on the first request.
2026-06-25 10:57:50 +08:00
Claude 52624aacff docs: document every env var in .env.example + forward in compose
.env.example now lists every env var read by the app, with a comment
explaining the meaning and the default. The previously-missing ones
(from the common/config.py refactor) are added:
  - SPARK_EXECUTOR_DATA_DIR
  - SPARK_EXECUTOR_JOBS_DIR
  - SPARK_EXECUTOR_LOG_LEVEL

docker-compose.yml forwards all of them with sensible container-side
defaults (SPARK_EXECUTOR_DATA_DIR=/app/data, which is the volume
mount point from the host).

Split the env block in compose into two commented sections
('common/config.py knobs' vs 'gunicorn.conf.py knobs') so it's clear
which file each one is consumed by.

The GUNICORN_LOGLEVEL entry is also added (was missing). All other
GUNICORN_* knobs were already documented.

116/116 still pass.
2026-06-25 10:54:11 +08:00
Claude 6bcf3b9ac9 feat: run FastAPI under gunicorn with uvicorn ASGI workers
Production entrypoint switch:
  - pyproject.toml: add gunicorn>=23.0 dep
  - gunicorn.conf.py: env-var-driven config (bind, workers, threads,
    timeout, graceful_timeout, keepalive, log level, access-log format)
  - Dockerfile: CMD gunicorn main:app (auto-loads gunicorn.conf.py from
    WORKDIR /app)
  - docker-compose.yml: forward GUNICORN_WORKERS / GUNICORN_TIMEOUT /
    GUNICORN_BIND
  - .env.example: document the new tunables

Why gunicorn over standalone uvicorn for production:
  - Process supervision: master restarts crashed workers, restarts on
    memory leaks
  - Graceful shutdown: SIGTERM drains workers in-flight
  - Multi-worker: concurrent requests actually run in parallel
  - Standard ops: k8s readiness probes, log aggregators, etc. all know
    gunicorn

Why uvicorn workers (not sync workers): gunicorn can't natively serve
ASGI; uvicorn.workers.UvicornWorker is the canonical way to run an
ASGI app under gunicorn.

Defaults:
  - 2 workers (small MCP service; raise for high concurrency)
  - 1 thread per worker (no blocking I/O)
  - 120s timeout (yarn logs can be slow; uvicorn's 30s default is too
    tight)

Verified: gunicorn boots, lifespan runs (14 tools logged), MCP
initialize + tools/list + tools/call all work, /openapi.json = 200,
multiple gunicorn worker processes visible in ps.

uv sync picked up gunicorn 26.0.0. Tests still 116/116.
2026-06-25 10:44:25 +08:00
Claude 504447048d feat(docker-compose.yml): remove healthcheck 2026-06-24 18:23:15 +08:00
Claude ce8dc1e2ba feat: add docker-compose.yml + .env.example
docker-compose.yml:
- Builds the Dockerfile, exposes :8000
- Mounts ./data (persistent) and ./hadoop-conf (ro) for cluster configs
- Forwards YARN_RESOURCE_MANAGER_URL via env var (fallback for REST client)
- Healthcheck via /openapi.json (cheap liveness probe; MCP initialize
  handshake would be more accurate but requires session-id plumbing)
- restart: unless-stopped for production
- Resource limit section commented out (server itself is lightweight)

.env.example:
- Documents the env vars compose expects
- .env is gitignored
2026-06-24 18:20:16 +08:00