# Docker 容器启动处理流程 本文档说明 MoviePilot V3 镜像从 Docker 创建容器到后端进入 `ready` 状态的完整处理链,覆盖 `docker/` 下的控制脚本、更新事务、依赖自愈、浏览器和证书准备、Nginx、Python lifespan、 异常保活与退出清理。 本文基于 `v3` 分支 2026-09-07 的实现整理。实际行为以当前源码为准。 ## 1. 文件职责 | 文件 | 运行位置与职责 | | --- | --- | | `docker/Dockerfile` | 构建 Python 环境、后端、前端、插件、站点资源和容器控制面;声明 `tini` 入口与 readiness 健康检查。 | | `docker/launcher.sh` | 构建时复制为镜像根目录 `/entrypoint.sh`;以 root 校验、选择并固化本轮启动使用的控制脚本。 | | `docker/entrypoint.sh` | 真正的容器启动编排器;加载配置,驱动更新、权限、浏览器和证书准备,最后启动 supervisor。 | | `docker/backend.sh` | supervisor 托管的后端进程命令,负责工作目录、权限和 Python 进程。 | | `docker/supervisord.conf` | 容器内 supervisor 配置,同时托管 Nginx 和后端。 | | `docker/update.sh` | 被 `entrypoint.sh` source;处理未完成更新恢复、Dev 更新、依赖同步和载荷事务,不再安装 Release 程序包。 | | `docker/update-worker.sh` | 由 Supervisor 以 root 按需运行;将已确认的 Release/资源制品替换到 Docker 程序目录,然后请求入口重新加载新代码。 | | `docker/browser.sh` | 被 `entrypoint.sh` source;选择持久化 CloakBrowser 缓存、校正权限并按需安装浏览器内核。 | | `docker/cert.sh` | 被 `entrypoint.sh` source;校验证书、按需安装 acme.sh、签发证书并配置续期任务。 | | `docker/nginx.template.conf` | 由环境变量渲染为 `/etc/nginx/nginx.conf`,提供前端静态文件、API 和 SSE 反向代理。 | | `docker/nginx.common.conf` | HTTP/HTTPS server 共用的前端、API、SSE 和静态资源规则。 | ## 2. 总体调用链 ```text Docker -> tini -g -> /entrypoint.sh # 镜像内置 launcher.sh -> 校验并选择控制脚本代际 -> /run/moviepilot/control//entrypoint.sh -> 加载 /config/app.env -> 渲染 Nginx 配置 -> source update.sh -> 恢复未完成更新 -> 仅执行 Dev 更新 -> 必要时用更新后的控制脚本重新 exec 一次 -> source browser.sh -> 映射 PUID/PGID -> 检查并按需恢复 Python 运行依赖 -> 校正目录和缓存权限 -> 准备 CloakBrowser 内核 -> source cert.sh -> 启动前端 Nginx -> supervisord -> Nginx -> gosu moviepilot python3 app/main.py -> 按需启动 root update-worker.sh 安装已确认 Release/资源制品 -> Uvicorn/FastAPI lifespan -> 数据库迁移和全部生命周期组件 -> /health/ready 返回 200 ``` 镜像的 `ENTRYPOINT` 为: ```text /usr/bin/tini -g -- /entrypoint.sh ``` `tini -g` 负责回收子进程并把停止信号发送给整个进程组。根目录 `/entrypoint.sh` 不是业务 `entrypoint.sh`,而是构建时从 `launcher.sh` 复制出的最小可信启动器。 ## 3. 控制脚本可信选择 ### 3.1 为什么不直接执行 `/app/docker/entrypoint.sh` Docker 的 Dev 更新和 root 更新 worker 都可能整体替换 `/app`。如果当前 Shell 正在从 `/app/docker` 继续 source 其他脚本,可能出现同一次启动混用新旧脚本的情况。因此 launcher 会先选择完整的一代控制脚本, 再复制到只属于本轮启动的运行时快照目录。 候选目录: | 优先级 | 目录 | 用途 | | --- | --- | --- | | 1 | `/app/docker` | 当前源码携带的控制脚本。 | | 特殊恢复 | `/app.__update_previous__/docker` | 更新停留在 `prepared` 且旧代完整可信时,用旧控制脚本恢复事务。 | | 兜底 | `/usr/local/lib/moviepilot/control` | 镜像构建时固化的控制脚本,不随 `/app` 更新。 | ### 3.2 信任检查 `source_bundle_is_trusted()` 对源码控制目录执行以下检查: 1. 目录及其上级路径不能是符号链接。 2. 目录及其上级路径必须归 root 所有,且 group/other 不可写。 3. 目录下所有 `.sh` 都必须是普通文件,不能是符号链接。 4. 必须至少包含 `entrypoint.sh`、`update.sh`、`browser.sh`、`cert.sh`。 5. 所有脚本必须通过 `bash -n`。 6. 按文件名排序后计算每个脚本的 SHA-256,再计算整组 manifest 的 SHA-256 作为代际编号。 通过检查的脚本会复制到: ```text /run/moviepilot/control// ``` 快照目录权限为 `0700`,脚本权限为 `0500`。launcher 导出 `MP_CONTROL_DIR` 和 `MP_CONTROL_GENERATION` 后,`exec` 快照中的 `entrypoint.sh`。 如果源码脚本不可信或快照失败,会回退镜像内置脚本;镜像内置脚本自身也必须通过完整性和语法检查, 否则容器拒绝启动。 ### 3.3 更新恢复时的代际选择 `/config/temp/__update_pending__` 的值会影响 launcher: | 状态 | 控制脚本选择原则 | | --- | --- | | `prepared` | 优先使用可信的旧代 `/app.__update_previous__/docker`;旧代不存在时继续检查当前源码。 | | `dependencies` / `blocked` | 不使用旧代控制脚本,避免数据库或当前载荷已经前进后执行缺少新迁移的旧逻辑。 | | `committed` 或无标记 | 使用当前可信源码,失败时回退镜像内置版本。 | ## 4. 配置加载和 Nginx 渲染 ### 4.1 基础环境 业务 `entrypoint.sh` 首先设置: - `VENV_PATH` 默认 `/opt/venv`,并前置到 `PATH`; - `UV_BIN` 默认 `/usr/local/bin/uv`; - `CONFIG_DIR` 默认 `/config`; - `PACKAGE_CACHE_ROOT` 默认 `/config/.cache`; - `UV_CACHE_DIR` 默认 `/config/.cache/uv`。 ### 4.2 `app.env` 优先级 脚本解析 `${CONFIG_DIR}/app.env`,配置优先级为: ```text 容器进程环境变量(包括显式空字符串) > /config/app.env > 脚本内置默认值 ``` 普通变量只加载脚本白名单中的启动配置;`ACME_ENV_*` 变量额外透传给证书流程。`app.env` 使用 Python 端 `set_key(..., quote_mode="always")` 对应的单引号格式,未按规范包裹的值只按字面量解析并输出告警。 ### 4.3 Nginx 配置 `render_nginx_config()` 使用一次 `envsubst` 生成 `/etc/nginx/nginx.conf`: - HTTP 默认监听 `NGINX_PORT=3000`; - 后端 upstream 默认指向 `127.0.0.1:3001`; - `ENABLE_SSL=true` 时追加 HTTPS server,证书固定读取 `/config/certs/latest/`; - `/` 服务 `/public` 下的前端文件; - `/api`、SSE 和 CookieCloud 路径转发到后端; - Service Worker、Manifest、普通静态资源分别使用适合更新语义的缓存策略。 ## 5. 启动前更新处理 `entrypoint.sh` 在 `/` 目录 source 当前控制快照中的 `update.sh`。脚本只定义函数和初始化更新状态, 随后由 entrypoint 按顺序调用恢复与更新入口。 ### 5.1 一次性 Dev 更新 如果存在: ```text /config/temp/moviepilot.pending_dev_update ``` entrypoint 会删除该标记,并只在本次启动中把 `MOVIEPILOT_UPDATE_DEV` 临时设为 `true`。更新阶段结束后 恢复原值,避免把一次性操作变成永久自动更新。 ### 5.2 未完成更新恢复 更新事务使用以下目录: ```text /app.__update_previous__ /public.__update_previous__ /config/temp/__update_pending__ ``` 状态含义与启动处理: | 状态 | 含义 | 下次启动处理 | | --- | --- | --- | | `prepared` | 新载荷已准备,事务开始,但尚未确认依赖阶段或提交。 | 恢复旧载荷并清理事务;没有备份时按当前完整载荷结束未完成事务。 | | `dependencies` | 正在同步新清单对应的共享虚拟环境。 | 当前 `/app`、`/public` 完整时保留当前载荷,改记 `blocked`,交给启动前依赖自愈;载荷不完整时恢复旧备份。 | | `blocked` | 已决定保留当前载荷,等待依赖自愈。 | 继续保留当前载荷;依赖自愈成功后清理旧备份和事务标记。 | | `committed` | 新载荷已切换完成,只剩旧备份清理。 | 清理旧备份并删除事务标记。 | 依赖阶段不能盲目回退旧 `/app`:数据库可能已经由当前程序迁移到新 revision,旧程序未必包含对应 Alembic migration。保留当前载荷并恢复其依赖,可以避免形成“新数据库 + 旧源码”的不可启动组合。 ### 5.3 已准备的 Release/资源更新 稳定版更新不在容器启动时联网检查 GitHub Release,也不在 `update.sh` 中比较版本号或替换程序。 后台更新服务负责下载和校验制品;用户确认安装后生成: ```text /config/temp/moviepilot-update/prepared.json /config/temp/moviepilot-update/install.json /config/temp/moviepilot-update/state.json ``` 随后 `SystemHelper` 请求 Supervisor 启动 root 更新 worker,处理顺序: 1. root worker 识别 `application`、`resources` 目标。 2. 校验后端、前端和资源文件存在且 SHA-256 一致。 3. 应用更新时解压后端和前端到临时目录,保留当前插件运行目录和 V3 站点资源。 4. 同时包含资源目标时,把已准备资源写入新后端的 `app/application/site/`。 5. 只有资源目标时,使用临时目录和备份目录原子替换当前源码携带的资源文件。 6. 后端依赖清单变化时,由 root worker 使用共享虚拟环境同步锁定依赖。 7. 源码和资源替换完成后消费下载清单,写入重启标记并关闭 Supervisor;外层入口重新执行 launcher,加载新代码。 8. 校验、依赖或替换失败时不关闭当前服务,写入 `state.json` 的可重试失败状态。 如果 worker 尚未启动就发生外部容器重启,入口会在启动 Supervisor 前调用同一个 root 安装器兜底消费 `install.json`,成功后重新执行 launcher;这条恢复路径也不经过 `update.sh`。 因此,制品下载完成后仍保留用户确认安装这一安全边界;确认后先替换 Docker 中的程序目录,重启只负责加载已经落盘的新代码。 ### 5.4 Dev 自动更新 仅当 `MOVIEPILOT_UPDATE_DEV=true` 时(首次升级兼容尚未迁移且未配置新开关的旧 `MOVIEPILOT_AUTO_UPDATE=dev`),启动脚本会联网获取 `v3` 分支源码和最新 V3 前端 Release。 GitHub 访问按 `GITHUB_PROXY`、`PROXY_HOST`、直连顺序选择;包索引按 `PIP_PROXY`、`PROXY_HOST`、 直连顺序选择。 后端或前端下载失败时不替换当前载荷。资源下载失败时优先复用当前资源;没有可用旧资源才终止更新。 ### 5.5 载荷切换事务 Release root worker 的应用更新提交顺序为: 1. 后台下载后,在 worker 中解压并验证后端和前端。 2. 暂存插件和站点资源。 3. 写入 `prepared`。 4. 依赖清单变化时写入 `dependencies`,再同步临时后端声明的依赖。 5. 把当前 `/app`、`/public` 移到旧代备份。 6. 把临时 `App`、`dist` 移到 `/app`、`/public`。 7. 写入 `committed`。 8. 删除旧代备份和事务标记。 Dev 更新仍由 `update.sh` 使用同一组事务标记处理;非 Dev 的制品替换只在 `app.adapters.system.update.SystemUpdateManager` 和 `docker/update-worker.sh` 中执行。 依赖同步固定使用当前虚拟环境解释器,并执行等价于: ```text uv sync --project --locked --inexact --no-dev --no-install-project \ --python /opt/venv/bin/python3 --no-default-groups --group ``` `runtime-profile` 由 `app.runtime.dependencies.profile` 根据标准 CPython 或 free-threaded CPython 选择。 回滚历史载荷时仍保留旧 `app/runtime/dependencies.py` 选择器兼容,防止旧快照无法恢复依赖。 ### 5.6 控制脚本更新后重入 Release 或 Dev 应用更新成功后,entrypoint 通过根目录 launcher 的 `--source-generation` 重新计算 `/app/docker` 代际。 如果新代际与当前 `MP_CONTROL_GENERATION` 不同,会: ```text exec /entrypoint.sh --post-update-reexec ``` launcher 设置“更新已完成”和“已经重入”标志,新 entrypoint 不会重复安装更新。单次启动只允许重入一次; 如果控制脚本再次变化,会终止启动以避免无限循环。 ## 6. 用户、依赖、浏览器和文件权限 ### 6.1 运行用户映射 entrypoint 使用 `PUID`、`PGID` 修改镜像内 `moviepilot` 用户和组。后端、浏览器安装及 doctor 默认通过 `gosu moviepilot:moviepilot` 执行;Release 更新 worker 明确以 root 运行来替换 root 所有的 `/app` 和 `/public`,不会把运行权限提升给后端;`START_NOGOSU=true` 仅用于不降权的特殊运行场景。即使 `PUID/PGID` 设置为非 0,内置重启仍然可用,因为 Supervisor socket 会使用映射后的 `moviepilot` 组权限;但容器入口 本身必须以 root 启动,不能额外使用 Docker 的 `--user` 覆盖入口用户。 ### 6.2 后端依赖自愈 启动后端前先执行轻量探针: ```text /opt/venv/bin/python3 -m app.doctor.dependencies ``` 探针检查 Web 栈、中文分词和转换等启动关键能力。成功时不会执行 `uv sync`;失败时才选择包源并对 当前 `/app` 执行锁定依赖同步,随后再次探测。 包源不可用、同步失败或二次探测失败时,不再拉起后端,而是进入 Docker 诊断保活流程。 ### 6.3 CloakBrowser 缓存 缓存目录选择顺序: 1. 显式绝对路径 `CLOAKBROWSER_CACHE_DIR`; 2. 已可用的 `/config/.browser/cloakbrowser`; 3. 已可用或单独挂载的历史 `${HOME}/.cloakbrowser`; 4. 新安装默认 `/config/.browser/cloakbrowser`。 显式目录不能是 `/`、`CONFIG_DIR` 或 `HOME` 本身。脚本先确认目录可由 `moviepilot` 读写,再用 `cloakbrowser.binary_info()` 判断内核是否可执行;缺失时以 `moviepilot` 用户安装,失败只告警,首次实际 使用浏览器时仍可重试。 ### 6.4 权限校正 默认启动避免递归扫描大体积浏览器缓存,只处理缓存根和直接子项。主要处理: - V3 站点资源目录; - 插件运行目录根; - `HOME` 和 `CONFIG_DIR` 下除浏览器缓存外的内容; - 配置目录外的自定义 uv 缓存; - Nginx 运行和日志目录; - `/etc/hosts` 与 `/tmp`。 `MOVIEPILOT_FORCE_CHOWN=true` 才会递归修复 `/app`、`/public` 和浏览器缓存。修复 `/app` 时会保留 `/app/docker` 为 root 所有且不可由 group/other 写入,保证下次 launcher 仍可信任源码控制脚本。 ## 7. 证书与前置服务 ### 7.1 证书处理 `cert.sh` 临时启用 `errexit` 和 `pipefail`,结束后恢复调用方原有 Shell 选项。 | 配置 | 行为 | | --- | --- | | `ENABLE_SSL=false` | 跳过证书处理。 | | `ENABLE_SSL=true`、`AUTO_ISSUE_CERT=false` | 要求 `/config/certs/latest/fullchain.pem` 和 `privkey.pem` 已存在。 | | `ENABLE_SSL=true`、`AUTO_ISSUE_CERT=true` | 要求 `SSL_DOMAIN` 和 `DNS_PROVIDER`;按需安装 acme.sh、加载 `ACME_ENV_*`、签发并安装证书。 | 自动签发证书保存到 `/config/certs//`,并维护 `/config/certs/latest` 符号链接。存在 `cron` 时, 脚本写入 `/etc/cron.d/acme`,每天 03:00 执行续期检查。续期任务配置失败不会阻断已有证书启动。 ### 7.2 Nginx 和 supervisor 证书检查完成后,entrypoint 以前台模式启动 supervisor。supervisor 同时托管 `moviepilot-nginx` 与 `moviepilot-backend`,两者异常退出时自动拉起;显式安装更新时还会按需启动 root worker。控制 socket 位于 `/run/moviepilot/supervisor.sock`,权限为 `root:moviepilot`、`0770`,后端运行用户可访问;控制面同时启用认证,密码默认在每次容器启动时随机生成, 不写入配置卷。supervisor 本身显式以 root 运行以管理 Nginx,后端仍由 `backend.sh` 降权为 `moviepilot`;镜像不再挂载或代理 Docker Socket。 ## 8. Python 后端启动 entrypoint 设置 `UMASK` 后,以后台进程启动: ```text gosu moviepilot:moviepilot /opt/venv/bin/python3 app/main.py ``` `app/main.py` 的主要步骤: 1. 修正直接执行脚本时的 `sys.path`,只让项目根目录作为应用导入入口。 2. 设置进程标题并读取运行配置。 3. 校验进程拓扑;全功能模式要求单 worker,安全模式允许降级拓扑。 4. 注册 SIGINT/SIGTERM 处理器。 5. 生产单进程模式创建 `MoviePilotServer` 并运行 Uvicorn。 6. FastAPI 应用通过 `app.factory.create_app()` 安装公开健康探针、中间件、异常处理和 lifespan。 ### 8.1 FastAPI lifespan 启动顺序 生命周期组件按 `start_order` 串行启动: | 顺序 | 组件 | 关键处理 | | ---: | --- | --- | | 1 | 文件日志 | 创建本次 lifespan 独占的文件日志 writer。 | | 5 | 后台任务登记器 | 建立统一的后台任务所有权和关停入口。 | | 10 | 数据库准备 | 校验迁移 lineage,按需备份,执行建表/Alembic upgrade,再确认唯一 head。 | | 20 | HTTP 基础能力 | 配置默认 User-Agent 和共享异步传输。 | | 25 | 站点访问端口 | 注入站点访问所需的网络、浏览器和 OCR 适配。 | | 26 | Chain 外部端口 | 注入 Chain 使用的外部系统能力。 | | 27 | Chain 网络端口 | 注入同步网络和系统适配。 | | 30 | 领域依赖装配 | 注入识别规则、DNS、系统和 Rust 能力。 | | 40 | 数据库引擎预热 | 物化同步与异步数据库引擎。 | | 50 | 数据库连接预算 | 校验当前部署的连接池预算。 | | 60 | 路由 | 注册主程序 API 路由。 | | 65 | 插件运行时装配 | 构造插件市场和插件运行时依赖。 | | 70 | 模块服务 | 构造唯一 HostRuntime、数据库 worker、配置、Agent/Chain/资源/事件等核心服务。 | | 72 | 插件服务装配 | 发布插件安装、目录和动态路由服务。 | | 75 | 消息队列 | 创建共享消息队列。 | | 80 | 插件备份恢复 | 普通模式下恢复启动前插件备份。 | | 90 | 插件 | 普通模式下加载插件。 | | 100 | 定时器 | 普通模式下启动 Scheduler 和 Agent 定时任务。 | | 110 | 监控器 | 普通模式下启动目录监控。 | | 120 | 待处理整理回放 | 普通模式下恢复未完成整理任务。 | | 130 | 命令服务 | 普通模式下注册命令入口。 | | 140 | 工作流 | 普通模式下启动工作流后台服务。 | | 150 | 插件同步与启动收尾 | 登记插件同步收尾任务,由 TaskRegistry 统一管理。 | 其中“模块服务”内部还会组装当前 lifespan 唯一的配置、数据库、认证、运行时、Agent、Chain、Outbox、 Transfer、Workflow 和 MoviePilot Server 服务,注册站点资源版本读取器、事件处理器和模块管理器。 `MOVIEPILOT_SAFE_MODE=true` 时跳过标记为 `NORMAL_ONLY` 的插件、调度器、监控、命令和工作流等组件, 但数据库、路由、核心模块服务和后台诊断入口仍会启动。 ### 8.2 数据库就绪边界 数据库准备先读取当前 revision 和代码唯一 head: - revision 不可识别、存在多个 current/head、或 current 不是目标 head 的祖先时直接失败; - 已标记的旧数据库需要迁移时,先 Alembic upgrade,再按当前元数据补表; - 新库或未标记旧库先建表,再执行 Alembic; - 配置允许时,迁移前创建数据库备份; - 迁移结束后再次验证数据库已经到达当前唯一 head。 只有该阶段完成,应用健康状态才标记 `database_ready`。 ### 8.3 readiness 所有启用的 fail-fast 启动组件成功后,lifespan 才把应用标记为 `ready`。 Dockerfile 的 `HEALTHCHECK` 每 30 秒请求 `http://127.0.0.1:${PORT}/health/ready`:数据库迁移和完整 lifespan 成功后返回 200;启动、 失败或关停阶段返回 503。`/health/live` 只表示进程和事件循环仍可响应。 ## 9. 异常、重启和退出 ### 9.1 信号退出 容器的 supervisor 接收 SIGINT/SIGTERM 后按顺序停止受管进程: 1. 停止前端 Nginx; 2. 等待 Python 完成 lifespan 逆序关停; 3. supervisor 退出,容器按原退出状态结束。 Python lifespan 会先撤销 readiness,再按组件声明的 `stop_order` 停止工作流、命令、插件、事件、Agent、 整理任务、模块服务、数据库和日志等资源。启动中途失败时,只清理已经启动或正在启动的组件。 ### 9.2 应用内重启 普通应用重启通过本地 `supervisorctl restart all` 同时重启 Nginx 和后端。确认安装 Release 时, `SystemHelper` 只启动 root `moviepilot-update-worker`;worker 先替换 `/app`、`/public` 或站点资源, 再写入 `moviepilot.pending_supervisor_restart` 并执行 `supervisorctl shutdown`。外层 entrypoint 看到标记后 重新执行 launcher,加载新代码。Dev 更新仍通过一次性 Dev 标记关闭 Supervisor,再由 entrypoint 调用 `update.sh`。这样更新包不会因只重启受管进程而停留在暂存目录;整个过程不访问 Docker API,也不依赖 Docker restart policy。 ### 9.3 异常诊断 后端异常退出由 supervisor 自动拉起;启动前的依赖、更新或迁移失败会直接终止容器启动并保留日志。 ## 10. 关键运行文件 | 路径 | 作用 | | --- | --- | | `/entrypoint.sh` | 镜像内置 launcher。 | | `/usr/local/lib/moviepilot/control/` | 镜像构建时固化的控制脚本。 | | `/run/moviepilot/control//` | 本轮启动执行的只读控制快照。 | | `/app` | 当前后端源码和插件运行目录。 | | `/public` | 当前前端静态文件。 | | `/config/app.env` | Docker 启动脚本和应用配置。 | | `/config/.cache/uv` | 默认 uv 持久缓存。 | | `/config/.browser/cloakbrowser` | 默认 CloakBrowser 持久缓存。 | | `/config/temp/__update_pending__` | 载荷切换事务状态。 | | `/app.__update_previous__` | 更新前后端备份。 | | `/public.__update_previous__` | 更新前前端备份。 | | `/config/temp/moviepilot-update/` | 后台下载的 Release/资源包及安装状态。 | | `/config/temp/moviepilot.pending_supervisor_restart` | Release worker 已替换程序、等待入口重新加载的标记。 | | `/config/temp/moviepilot.pending_dev_update` | 单次 Dev 更新请求。 | | `/run/moviepilot/supervisor.sock` | 容器内 supervisor 控制 socket,权限为 `root:moviepilot`、`0770`,并启用本次容器启动的认证凭据。 | | `/config/certs/latest/` | Nginx 使用的稳定证书路径。 | ## 11. 关键环境变量 | 变量 | 默认值 | 影响 | | --- | --- | --- | | `CONFIG_DIR` | `/config` | 配置、缓存、更新状态和证书根目录。 | | `PUID` / `PGID` | `0` / `0` | 映射 `moviepilot` 运行用户。 | | `UMASK` | `000` | 后端进程文件权限掩码。 | | `PORT` | `3001` | 后端监听和 readiness 端口。 | | `NGINX_PORT` | `3000` | HTTP 前端入口。 | | `MOVIEPILOT_AUTO_UPDATE` | `false` | 布尔开关,仅 `true` 开启后台版本检查和升级提醒;关闭后不检查主程序;`AUTO_UPDATE_RESOURCE=true` 时仍启用服务且只检查站点资源。稳定版下载及安装需手动确认。旧 `dev/release` 统一迁移为 `true`。 | | `MOVIEPILOT_UPDATE_DEV` | `false` | 独立布尔开关,`true` 触发 `update.sh` 的启动时 Dev 分支更新;旧 `dev` 在未显式配置此开关时保留跟踪偏好。 | | `MOVIEPILOT_SAFE_MODE` | `false` | 跳过普通模式专属的插件及后台控制面。 | | `MOVIEPILOT_FORCE_CHOWN` | `false` | 是否执行大范围递归权限修复。 | | `PACKAGE_CACHE_ROOT` | `/config/.cache` | 包管理缓存根目录。 | | `UV_CACHE_DIR` | `/config/.cache/uv` | uv 缓存目录。 | | `PIP_PROXY` | 空 | Python 包索引镜像。 | | `GITHUB_PROXY` | 空 | GitHub 下载 URL 前缀。 | | `PROXY_HOST` | 空 | GitHub/包命令使用的 HTTP(S) 代理。 | | `GITHUB_TOKEN` | 空 | Dev 更新访问 GitHub 时的令牌。 | | `CLOAKBROWSER_CACHE_DIR` | 自动选择 | 浏览器内核缓存位置。 | | `ENABLE_SSL` | `false` | 是否渲染并启用 HTTPS server。 | | `AUTO_ISSUE_CERT` | `false` | 是否自动签发证书。 | | `SSL_DOMAIN` / `SSL_EMAIL` / `DNS_PROVIDER` | 空 | acme.sh 签发参数。 | ## 12. 维护约束 后续修改 Docker 启动流程时应保持以下边界: 1. 控制脚本必须按完整代际执行,不能在同一次启动中直接混用更新前后的 `/app/docker/*.sh`。 2. Release 更新由后台下载、用户确认和 root worker 驱动;`update.sh` 不得恢复 Release 程序/资源替换、启动时 GitHub Release 查询或 Shell 版本比较。 3. 更新载荷与共享虚拟环境必须作为一个可恢复事务处理,不能留下新源码配旧依赖或旧源码配新数据库的混合状态。 4. 标准 V3 与 V3t 依赖恢复必须复用 `app.runtime.dependencies.profile`,不能使用默认组覆盖当前 ABI profile。 5. 站点资源只安装到 `app/application/site/`;历史目录仅用于更新旧载荷时读取兼容资源。 6. readiness 只能在数据库 head 校验和完整 lifespan 成功后发布,不能退化为进程存在或普通接口可访问。 7. 后端异常保活是诊断机制,不是成功状态;外部编排必须继续以 `/health/ready` 判断可接流量。