diff --git a/offline-backend-20260512/.dockerignore b/offline-backend-20260512/.dockerignore new file mode 100644 index 00000000..ae7e815a --- /dev/null +++ b/offline-backend-20260512/.dockerignore @@ -0,0 +1,34 @@ +# Docker build context exclusions (context = offline-backend-20260512/) +.env +.env.* +!.env.example + +# Runtime / local data — must not enter the image +data/ +backend/.deer-flow/ +backend/.venv/ +backend/venv/ + +# Python caches +backend/**/__pycache__/ +backend/**/*.py[cod] +backend/.pytest_cache/ +backend/.ruff_cache/ +backend/.coverage +backend/htmlcov/ + +# IDE / OS +.git/ +.idea/ +.vscode/ +.DS_Store +Thumbs.db + +# Docs and tests (not needed in runtime image) +backend/tests/ +backend/docs/ +*.md +!backend/README.md + +# Large optional trees (uncomment if build context is too slow) +# backend/knowledge/ diff --git a/offline-backend-20260512/.gitattributes b/offline-backend-20260512/.gitattributes new file mode 100644 index 00000000..001b9d83 --- /dev/null +++ b/offline-backend-20260512/.gitattributes @@ -0,0 +1,3 @@ +# Shell scripts must use LF so Linux/bash can run them directly. +*.sh text eol=lf +scripts/*.sh text eol=lf diff --git a/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md b/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md index 359eaac3..e3aadf61 100644 --- a/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md +++ b/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md @@ -184,17 +184,36 @@ curl -s http://127.0.0.1:8001/openapi.json | head HOST_PORT=9001 bash deploy.sh up ``` -### 5.2 离线镜像模式(无法构建时) +### 5.2 离线镜像模式(内网/无法访问 docker.io 时必用) -若已提供预构建镜像 tar: +**典型失败日志:** + +```text +failed to fetch anonymous token: Get "https://auth.docker.io/token...": i/o timeout +``` + +说明服务器访问不了 Docker Hub,**不能在线 build**,必须用离线 tar。 + +**步骤 1:在有公网的机器上导出镜像** ```bash +bash scripts/export-offline-image.sh +# 产出: deerflow-backend-offline-image-20260505.tar +``` + +**步骤 2:上传到内网服务器部署目录** + +**步骤 3:内网服务器加载并启动** + +```bash +DEPLOY_MODE=offline \ IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \ IMAGE_TAG=deerflow-backend-offline:20260505 \ -DEPLOY_MODE=offline \ bash deploy.sh up ``` +若 tar 已在目录中,`deploy.sh` 会**自动检测**并切换为离线模式。 + 或分步: ```bash @@ -202,6 +221,25 @@ bash deploy.sh load IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up ``` +### 5.3 配置 Docker 镜像加速(可选,有内网镜像源时) + +编辑 `/etc/docker/daemon.json`: + +```json +{ + "registry-mirrors": [ + "https://你的内网或国内镜像地址" + ], + "dns": ["114.114.114.114", "223.5.5.5"] +} +``` + +```bash +sudo systemctl daemon-reload +sudo systemctl restart docker +bash deploy.sh build +``` + --- ## 6. 前端对接 @@ -268,6 +306,73 @@ bash deploy.sh restart ## 10. 故障排查 +### 10.0 Docker 未运行或无权限 + +报错示例: + +```text +[FAIL] Docker 未运行或无权限... +``` + +**第一步:看具体原因** + +```bash +docker info +``` + +**情况 A:permission denied(最常见)** + +说明当前用户不在 `docker` 组: + +```bash +# 把当前用户加入 docker 组 +sudo usermod -aG docker "$USER" + +# 让组权限立即生效(或重新 SSH 登录) +newgrp docker + +# 验证 +docker info +docker ps + +# 再部署 +bash deploy.sh +``` + +若仍不行,确认 socket 权限: + +```bash +ls -l /var/run/docker.sock +# 期望类似: srw-rw---- root docker ... +``` + +**情况 B:Cannot connect to the Docker daemon** + +说明 Docker 服务没起来: + +```bash +sudo systemctl status docker +sudo systemctl start docker +sudo systemctl enable docker +docker info +bash deploy.sh +``` + +**情况 C:未安装 Docker** + +```bash +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker "$USER" +newgrp docker +bash deploy.sh +``` + +**临时绕过(仅测试用,不推荐生产)** + +```bash +sudo bash deploy.sh +``` + ### 10.1 容器启动后立即退出 ```bash @@ -281,6 +386,37 @@ bash deploy.sh logs - MySQL 连接失败(检查 `MYSQL_DATABASE_URL` 是否用了容器可达的 IP) - 缺少 `asyncmy`(MySQL 模式):确认 `backend/wheelhouse` 中有对应 wheel,或配置 Python 私服 +### 10.1.1 脚本报 `$'\r': command not found` 或 `pipefail: invalid option` + +这是 **Windows 换行符(CRLF)** 导致的。在 Linux 上执行: + +```bash +sed -i 's/\r$//' deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh switch_database_to_mysql.sh +chmod +x deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh +bash deploy.sh +``` + +或在 Windows 上传前,用 VS Code 右下角把文件编码改为 **LF**。 + +### 10.1.2 构建时报 `.venv cannot be used because it is not a valid Python environment` + +原因:开发机(尤其 Windows)上的 `backend/.venv` 被打包进 Docker 构建上下文,Linux 容器内无法使用。 + +解决:仓库已添加 `.dockerignore` 排除 `.venv`。更新代码后重新构建: + +```bash +bash deploy.sh build +# 或 +bash deploy.sh up +``` + +若仍报错,手动删除后再构建: + +```bash +rm -rf backend/.venv +bash deploy.sh build +``` + ### 10.2 健康检查失败 ```bash diff --git a/offline-backend-20260512/backend/Dockerfile b/offline-backend-20260512/backend/Dockerfile index 94cfbe5f..506b9d62 100644 --- a/offline-backend-20260512/backend/Dockerfile +++ b/offline-backend-20260512/backend/Dockerfile @@ -47,8 +47,9 @@ COPY backend ./backend # Install dependencies with cache mount # When UV_EXTRAS is set (e.g. "postgres"), installs optional dependencies. +# Remove host .venv if present — Windows/macOS venvs break Linux container builds. RUN --mount=type=cache,target=/root/.cache/uv \ - sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}" + sh -c "cd backend && rm -rf .venv && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}" # ── Stage 2: Dev ────────────────────────────────────────────────────────────── # Retains compiler toolchain from builder so startup-time `uv sync` can build diff --git a/offline-backend-20260512/deploy.sh b/offline-backend-20260512/deploy.sh index 49b2d470..be0dd967 100644 --- a/offline-backend-20260512/deploy.sh +++ b/offline-backend-20260512/deploy.sh @@ -37,6 +37,32 @@ IMAGE_TAR="${IMAGE_TAR:-${SCRIPT_DIR}/deerflow-backend-offline-image.tar}" DEPLOY_MODE="${DEPLOY_MODE:-build}" SKIP_BUILD="${SKIP_BUILD:-0}" +resolve_offline_tar() { + if [ -f "${IMAGE_TAR}" ]; then + return 0 + fi + local candidate + for candidate in \ + "${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar" \ + "${SCRIPT_DIR}/deerflow-backend-offline-image.tar"; do + if [ -f "${candidate}" ]; then + IMAGE_TAR="${candidate}" + return 0 + fi + done + return 1 +} + +# 内网服务器通常无法访问 docker.io,检测到离线 tar 时自动切换模式 +resolve_offline_tar || true +if [ -f "${IMAGE_TAR}" ] && [ "${DEPLOY_MODE}" = "build" ]; then + DEPLOY_MODE="offline" + if [ "${IMAGE_TAG}" = "deerflow-backend:latest" ]; then + IMAGE_TAG="deerflow-backend-offline:20260505" + fi + warn "检测到离线镜像 ${IMAGE_TAR},已自动切换为 DEPLOY_MODE=offline" +fi + GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' @@ -92,7 +118,28 @@ ensure_env() { check_docker() { require_cmd docker - docker info >/dev/null 2>&1 || fail "Docker 未运行或无权限,请确认 docker 服务已启动且当前用户在 docker 组" + + local docker_err + docker_err="$(docker info 2>&1)" || { + echo "${docker_err}" >&2 + echo >&2 + if echo "${docker_err}" | grep -qiE 'permission denied|connect: permission'; then + fail "当前用户无权访问 Docker。请执行: + sudo usermod -aG docker \$USER + newgrp docker +然后重新运行: bash deploy.sh + +临时方案(不推荐长期使用): sudo bash deploy.sh" + fi + if echo "${docker_err}" | grep -qiE 'cannot connect|Is the docker daemon running|connection refused'; then + fail "Docker 服务未运行。请先启动: + sudo systemctl start docker + sudo systemctl enable docker +然后重新运行: bash deploy.sh" + fi + fail "Docker 不可用。请先在服务器上执行 docker info 查看详细错误" + } + docker compose version >/dev/null 2>&1 || fail "未找到 docker compose,请安装 Docker Compose V2" } @@ -122,16 +169,33 @@ load_offline_image() { ok "镜像已存在: ${IMAGE_TAG}" return 0 fi - [ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR},请设置 IMAGE_TAR 或先执行 bash deploy.sh build" + [ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR} + +内网服务器无法访问 docker.io 时,请: + 1. 在有公网的机器上执行 bash scripts/export-offline-image.sh 导出 tar + 2. 上传到本目录后执行: DEPLOY_MODE=offline bash deploy.sh up" info "加载离线镜像: ${IMAGE_TAR}" - docker load -i "${IMAGE_TAR}" + local load_out loaded_tag + load_out="$(docker load -i "${IMAGE_TAR}")" + echo "${load_out}" + loaded_tag="$(echo "${load_out}" | sed -n 's/^Loaded image: //p' | tail -n1)" + if [ -n "${loaded_tag}" ] && ! docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then + info "将 ${loaded_tag} 标记为 ${IMAGE_TAG}" + docker tag "${loaded_tag}" "${IMAGE_TAG}" + fi + docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1 || fail "镜像加载后未找到 ${IMAGE_TAG},请检查 IMAGE_TAG 是否与 tar 内标签一致" ok "镜像加载完成: ${IMAGE_TAG}" } build_image() { - info "构建 Docker 镜像: ${IMAGE_TAG}" + info "构建 Docker 镜像: ${IMAGE_TAG}(需要能访问 docker.io / ghcr.io)" export IMAGE_TAG - "${COMPOSE[@]}" build gateway + if ! "${COMPOSE[@]}" build gateway; then + fail "在线构建失败。内网服务器请改用离线镜像: + DEPLOY_MODE=offline IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar bash deploy.sh up + +或在 /etc/docker/daemon.json 配置 registry-mirrors 后重试。" + fi ok "镜像构建完成" } @@ -184,7 +248,11 @@ cmd_up() { fi info "启动容器..." - "${COMPOSE[@]}" up -d --remove-orphans + if [ "${DEPLOY_MODE}" = "offline" ] || docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then + "${COMPOSE[@]}" up -d --no-build --remove-orphans + else + "${COMPOSE[@]}" up -d --remove-orphans + fi ok "容器已启动: ${CONTAINER_NAME}" echo echo " API 地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 127.0.0.1):${HOST_PORT}" diff --git a/offline-backend-20260512/scripts/export-offline-image.sh b/offline-backend-20260512/scripts/export-offline-image.sh new file mode 100644 index 00000000..d9af23d1 --- /dev/null +++ b/offline-backend-20260512/scripts/export-offline-image.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# 在有公网的环境构建并导出离线镜像 tar,供内网服务器 docker load 使用。 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${SCRIPT_DIR}" + +IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}" +OUTPUT="${OUTPUT:-${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar}" + +echo "构建镜像: ${IMAGE_TAG}" +IMAGE_TAG="${IMAGE_TAG}" docker compose -p deerflow-backend -f docker-compose.yaml build gateway + +loaded="$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -F "${IMAGE_TAG}" | head -n1 || true)" +if [ -z "${loaded}" ]; then + # compose build 可能产出 deerflow-backend:latest,再打离线标签 + docker tag deerflow-backend:latest "${IMAGE_TAG}" 2>/dev/null || true +fi + +echo "导出到: ${OUTPUT}" +docker save -o "${OUTPUT}" "${IMAGE_TAG}" +ls -lh "${OUTPUT}" +echo +echo "上传到内网服务器后执行:" +echo " DEPLOY_MODE=offline IMAGE_TAR=${OUTPUT##*/} IMAGE_TAG=${IMAGE_TAG} bash deploy.sh up"