mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-09 23:30:13 +08:00
510 lines
27 KiB
Markdown
510 lines
27 KiB
Markdown
# Docker 容器启动处理流程
|
||
|
||
本文档说明 MoviePilot V3 镜像从 Docker 创建容器到后端进入 `ready` 状态的完整处理链,覆盖
|
||
`docker/` 下的控制脚本、更新事务、依赖自愈、浏览器和证书准备、Nginx、Python lifespan、
|
||
异常保活与退出清理。
|
||
|
||
本文基于 `v3` 分支 2026-09-07 的实现整理。实际行为以当前源码为准。
|
||
|
||
Agent 中文全文索引扩展在镜像的 `prepare_cjk` 阶段按目标架构编译,最终随 Python venv 放在
|
||
`/opt/venv/lib/moviepilot/libfts5_cjk.so`。构建时由所选 standard/free-threaded Python 完成真实
|
||
加载及两字中文查询验收;运行容器不需要编译器,挂载 `/config` 不影响扩展可见性。
|
||
旧镜像需要重新构建或更换镜像,源码热更新不会自动安装该原生文件。
|
||
|
||
```shell
|
||
docker exec <container> /opt/venv/bin/python /app/native/fts5_cjk/install.py --check
|
||
```
|
||
|
||
该命令只验证环境能力,不访问用户库。旧历史的 CJK 索引随后分批回填,检索结果通过
|
||
`index_status.messages_fts_cjk` 和 `search_path` 报告是否已经就绪及实际使用的路径。
|
||
|
||
## 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/<sha256>/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/<generation>/
|
||
```
|
||
|
||
快照目录权限为 `0700`,脚本权限为 `0500`。launcher 导出 `MP_CONTROL_DIR` 和
|
||
`MP_CONTROL_GENERATION` 后,`exec` 快照中的 `entrypoint.sh`。
|
||
|
||
如果源码脚本不可信或快照失败,会回退镜像内置脚本;镜像内置脚本自身也必须通过完整性和语法检查,
|
||
否则容器拒绝启动。
|
||
|
||
### 3.3 更新恢复时的代际选择
|
||
|
||
`/var/lib/moviepilot/update/__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__
|
||
/var/lib/moviepilot/update/__update_pending__
|
||
```
|
||
|
||
Dev 更新标记与程序载荷都位于容器可写层,只用于跨同一容器重启恢复;容器重建时它会和旧的
|
||
`/app`、`/public` 载荷一起丢弃,避免持久化卷残留旧事务标记而阻断新镜像启动。稳定版
|
||
Release/资源更新的 `prepared.json`、`install.json` 和 `state.json` 仍保存在 `/config`,不受此规则影响。
|
||
|
||
状态含义与启动处理:
|
||
|
||
| 状态 | 含义 | 下次启动处理 |
|
||
| --- | --- | --- |
|
||
| `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 <project> --locked --inexact --no-dev --no-install-project \
|
||
--python /opt/venv/bin/python3 --no-default-groups --group <runtime-profile>
|
||
```
|
||
|
||
`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/<domain>/`,并维护 `/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` 的插件、调度器、监控、命令和工作流等组件,
|
||
但数据库、路由、核心模块服务和后台诊断入口仍会启动。
|
||
|
||
插件安装 journal 的启动恢复按插件逐条处理:单个插件的恢复或事实校验失败会记录日志、保留 journal
|
||
供后续重试,并跳过本条恢复,不阻断其他插件和宿主启动;数据库读取或插件恢复服务本身的全局故障仍按
|
||
生命周期组件失败处理。
|
||
|
||
### 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 moviepilot-nginx moviepilot-backend` 同时重启 Nginx 和后端,
|
||
避免把按需运行的更新 worker 拉起。确认安装 Release 时,
|
||
`SystemHelper` 只启动 root `moviepilot-update-worker`;worker 先替换 `/app`、`/public` 或站点资源,
|
||
再写入 `moviepilot.pending_supervisor_restart` 并执行 `supervisorctl shutdown`。外层 entrypoint 看到标记后
|
||
重新执行 launcher,加载新代码。载荷替换期间外层 entrypoint、worker 的控制面调用和所有入口重入都使用 `/`
|
||
作为稳定工作目录,避免旧 `/app` 被删除后产生 `getcwd` 错误。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/<generation>/` | 本轮启动执行的只读控制快照。 |
|
||
| `/app` | 当前后端源码和插件运行目录。 |
|
||
| `/public` | 当前前端静态文件。 |
|
||
| `/config/app.env` | Docker 启动脚本和应用配置。 |
|
||
| `/config/.cache/uv` | 默认 uv 持久缓存。 |
|
||
| `/config/.browser/cloakbrowser` | 默认 CloakBrowser 持久缓存。 |
|
||
| `/var/lib/moviepilot/update/__update_pending__` | Dev 载荷切换事务状态;与 `/app`、`/public` 同属容器可写层。 |
|
||
| `/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` | 配置、缓存、Release/资源更新状态和证书根目录。 |
|
||
| `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` | 空 | GitHub API 与 Agent Issue/PR Skill 共用的服务端 Token;可在设置页或首次初始化页通过 Device Flow 或手动 PAT 配置。 |
|
||
| `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` 判断可接流量。
|