diff --git a/Dockerfile b/Dockerfile index 7fcbb49..e0196e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,27 +16,46 @@ ENV PYTHONUNBUFFERED=1 \ # asyncmy compiles a Cython extension, so a toolchain has to exist at build time. # It is left installed: purging it risks taking libmysqlclient with it, and a # slightly larger image is cheaper than a runtime that fails months later. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - build-essential pkg-config default-libmysqlclient-dev \ - && rm -rf /var/lib/apt/lists/* +# +# deb.debian.org is effectively unusable from this network -- it was pulling the +# 96 MB of build dependencies at roughly 13 kB/s, which puts a build at well over +# half an hour. Point apt at a domestic mirror; override APT_MIRROR when building +# from somewhere that does not need it. +# +# The libgl1/libxcb1/... group is not for a GUI: opencv-python links against X11 +# at import time, and tools/utils.py reaches cv2 through slider_util, so without +# them the *application* fails to import, not just some image utility. tzdata is +# here because TZ=Asia/Shanghai is silently ignored without it, which would put +# every stored timestamp in UTC. +# +# The pip mirror is set for the same reason as the apt one: this host's route to +# the public index is slow. +ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn +RUN set -eux; \ + for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources; do \ + if [ -f "$f" ]; then \ + sed -i "s|deb.debian.org|${APT_MIRROR}|g; s|security.debian.org|${APT_MIRROR}|g" "$f"; \ + fi; \ + done; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + build-essential pkg-config default-libmysqlclient-dev \ + libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 libxcb1 libgomp1 \ + tzdata; \ + rm -rf /var/lib/apt/lists/* WORKDIR /app -# Requirements first: this layer only rebuilds when the pins actually change. +# Requirements only -- this is the one layer that is expensive to build and +# changes rarely. ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple COPY requirements.txt ./ RUN pip install --no-cache-dir -i "$PIP_INDEX" -r requirements.txt -COPY . . - -# The WebUI bundle lives at api/webui, which is gitignored -- it is built on the -# workstation (`cd webui && npm run build`) and shipped inside the build context. -# Without this check a missing bundle is invisible until someone opens the page -# and gets the bare JSON stub from serve_frontend(). -RUN test -f api/webui/index.html \ - || (echo "ERROR: api/webui/index.html is missing. Run 'cd webui && npm run build' before building the image." >&2; exit 1) - +# The application code is deliberately NOT copied in. compose mounts it at /app, +# so a code change is "re-upload the tarball, restart the container" instead of +# an image rebuild. Treat this image as the dependency layer and nothing else; +# rebuild it when, and only when, requirements.txt or this file changes. EXPOSE 18051 # api.main reads MC_HOST / MC_PORT from the environment; compose supplies both. diff --git a/api/main.py b/api/main.py index 5be7df6..f1854b0 100644 --- a/api/main.py +++ b/api/main.py @@ -70,6 +70,16 @@ async def lifespan(_app: FastAPI): await init_db() + # The WebUI bundle is gitignored and built separately, so a deployment that + # forgot it would otherwise come up looking healthy and serve a bare JSON + # stub at "/" -- worth one loud line at boot rather than a puzzled operator. + if not os.path.exists(os.path.join(WEBUI_DIR, "index.html")): + print( + "[综合采集平台] 警告:未找到前端产物 api/webui/index.html," + "根路径只会返回一段 JSON。请先在 webui/ 下执行 npm run build。", + flush=True, + ) + generated = await ensure_initial_credential() if generated: # Printed once, on the run that creates it. There is no unauthenticated diff --git a/docker-compose.yml b/docker-compose.yml index e768cff..16db640 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,9 @@ services: TZ: Asia/Shanghai volumes: - # Per-run crawler output, read back by the monitor layer. Without this the - # data would live inside the container and vanish on every rebuild. - - ./data:/app/data + # The code is mounted rather than baked in, so shipping a change is + # "re-upload the tarball, restart" instead of an image rebuild. Only the + # dependencies live in the image, because those are the expensive part and + # they change rarely -- rebuild only when requirements.txt or the + # Dockerfile itself changes. + - ./:/app