build(docker): refresh system packages against a tracked baseline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
InfinityPacer
2026-10-22 14:59:42 +08:00
committed by InfinityPacer
parent a76fc9f579
commit cba9779a8a
6 changed files with 78 additions and 2 deletions

View File

@@ -106,6 +106,27 @@ jobs:
echo "digest=sha256:$(sha256sum app/agent/llm/resources/models.json | awk '{print $1}')" >> "$GITHUB_OUTPUT"
echo "Downloaded models.dev catalog ($(wc -c < app/agent/llm/resources/models.json) bytes)"
- name: Resolve System Refresh Key
id: system_refresh
run: |
# 刷新键决定镜像末尾系统安全更新层何时重建:每 ISO 周最多一次,
# docker/system-baseline 变化时立即重建。计算失败只降级为基线值,不阻断发布。
baseline=$(tr -d '[:space:]' < docker/system-baseline 2>/dev/null || true)
if ! [[ "$baseline" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "::warning::docker/system-baseline 缺失或格式无效,系统基线记为 unknown"
baseline=unknown
fi
week=$(date -u +%G-W%V 2>/dev/null || true)
if [[ "$week" =~ ^[0-9]{4}-W[0-9]{2}$ ]]; then
key="${week}-${baseline}"
else
echo "::warning::无法计算 ISO 周,系统刷新键仅使用系统基线"
key="${baseline}"
fi
echo "System refresh key: ${key}"
echo "baseline=${baseline}" >> "$GITHUB_OUTPUT"
echo "key=${key}" >> "$GITHUB_OUTPUT"
- name: Docker Meta
id: meta
uses: docker/metadata-action@v6
@@ -162,8 +183,10 @@ jobs:
MOVIEPILOT_PLUGINS_REF=${{ steps.payloads.outputs.plugins_revision }}
MOVIEPILOT_RESOURCES_REF=${{ steps.payloads.outputs.resources_revision }}
MOVIEPILOT_PYTHON_VARIANT=standard
MOVIEPILOT_SYSTEM_REFRESH_ID=${{ steps.system_refresh.outputs.key }}
labels: |
${{ steps.meta.outputs.labels }}
org.moviepilot.system-baseline=${{ steps.system_refresh.outputs.baseline }}
org.opencontainers.image.revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.source-revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.frontend-version=${{ steps.release_version.outputs.frontend_version }}
@@ -195,8 +218,10 @@ jobs:
MOVIEPILOT_PLUGINS_REF=${{ steps.payloads.outputs.plugins_revision }}
MOVIEPILOT_RESOURCES_REF=${{ steps.payloads.outputs.resources_revision }}
MOVIEPILOT_PYTHON_VARIANT=free-threaded
MOVIEPILOT_SYSTEM_REFRESH_ID=${{ steps.system_refresh.outputs.key }}
labels: |
${{ steps.meta_ft.outputs.labels }}
org.moviepilot.system-baseline=${{ steps.system_refresh.outputs.baseline }}
org.opencontainers.image.revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.source-revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.frontend-version=${{ steps.release_version.outputs.frontend_version }}

View File

@@ -172,6 +172,27 @@ jobs:
fi
echo "release_commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Resolve System Refresh Key
id: system_refresh
run: |
# 刷新键决定镜像末尾系统安全更新层何时重建:每 ISO 周最多一次,
# docker/system-baseline 变化时立即重建。计算失败只降级为基线值,不阻断发布。
baseline=$(tr -d '[:space:]' < docker/system-baseline 2>/dev/null || true)
if ! [[ "$baseline" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "::warning::docker/system-baseline 缺失或格式无效,系统基线记为 unknown"
baseline=unknown
fi
week=$(date -u +%G-W%V 2>/dev/null || true)
if [[ "$week" =~ ^[0-9]{4}-W[0-9]{2}$ ]]; then
key="${week}-${baseline}"
else
echo "::warning::无法计算 ISO 周,系统刷新键仅使用系统基线"
key="${baseline}"
fi
echo "System refresh key: ${key}"
echo "baseline=${baseline}" >> "$GITHUB_OUTPUT"
echo "key=${key}" >> "$GITHUB_OUTPUT"
- name: Docker Meta
id: meta
uses: docker/metadata-action@v6
@@ -231,8 +252,10 @@ jobs:
MOVIEPILOT_PLUGINS_REF=${{ steps.payloads.outputs.plugins_revision }}
MOVIEPILOT_RESOURCES_REF=${{ steps.payloads.outputs.resources_revision }}
MOVIEPILOT_PYTHON_VARIANT=standard
MOVIEPILOT_SYSTEM_REFRESH_ID=${{ steps.system_refresh.outputs.key }}
labels: |
${{ steps.meta.outputs.labels }}
org.moviepilot.system-baseline=${{ steps.system_refresh.outputs.baseline }}
org.opencontainers.image.revision=${{ steps.release_snapshot.outputs.release_commit }}
org.moviepilot.source-revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.release-snapshot-revision=${{ steps.release_snapshot.outputs.release_commit }}
@@ -265,8 +288,10 @@ jobs:
MOVIEPILOT_PLUGINS_REF=${{ steps.payloads.outputs.plugins_revision }}
MOVIEPILOT_RESOURCES_REF=${{ steps.payloads.outputs.resources_revision }}
MOVIEPILOT_PYTHON_VARIANT=free-threaded
MOVIEPILOT_SYSTEM_REFRESH_ID=${{ steps.system_refresh.outputs.key }}
labels: |
${{ steps.meta_ft.outputs.labels }}
org.moviepilot.system-baseline=${{ steps.system_refresh.outputs.baseline }}
org.opencontainers.image.revision=${{ steps.release_snapshot.outputs.release_commit }}
org.moviepilot.source-revision=${{ env.SOURCE_COMMIT }}
org.moviepilot.release-snapshot-revision=${{ steps.release_snapshot.outputs.release_commit }}

View File

@@ -367,6 +367,23 @@ RUN mkdir -p ${HOME} \
&& echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen \
&& locale-gen zh_CN.UTF-8
# 系统安全更新层:放在所有大层之后、--link 载荷层之前,刷新时只重建这一小层。
# CI 传入 "<ISO 年-周>-<系统基线>" 作为刷新键,每周最多重建一次;docker/system-baseline
# 变化时 COPY 内容变化,同样立即重建。未传键的本地构建按普通缓存规则复用。
# 基线文件写入镜像,供应用内更新判断当前镜像是否满足目标版本的 MIN_SYSTEM_BASELINE。
ARG MOVIEPILOT_SYSTEM_REFRESH_ID=""
COPY docker/system-baseline /etc/moviepilot/system-baseline
RUN set -eu; \
printf 'System refresh key: %s, baseline: %s\n' \
"${MOVIEPILOT_SYSTEM_REFRESH_ID:-unset}" "$(cat /etc/moviepilot/system-baseline)"; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends \
-o Dpkg::Options::=--force-confdef \
-o Dpkg::Options::=--force-confold \
upgrade; \
apt-get clean; \
rm -rf /var/lib/apt/lists/* /var/tmp/*
# 载荷按独立更新频率写入镜像,后端源码保持在最后一层。
WORKDIR /app

1
docker/system-baseline Normal file
View File

@@ -0,0 +1 @@
2026-10-22

View File

@@ -71,6 +71,8 @@ ci: improve docker build cache
- The primary Docker image bundles the backend (Python app), frontend static files (from `public/`), and resource data.
- Docker build and release are managed by CI. Do not manually trigger or alter the Docker release flow unless the task explicitly requires it.
- If a Dockerfile change is needed, update `Dockerfile` and verify the build locally before submitting.
- The image ends with a small system security update layer. CI rebuilds it at most once per ISO week; changing the ISO date in `docker/system-baseline` rebuilds it immediately and stamps the new date into `/etc/moviepilot/system-baseline`.
- `MIN_SYSTEM_BASELINE` in `version.py` is the oldest image baseline that may install this release through the in-app or Dev updater. Raise it only for severe system-level vulnerabilities, and set it to the same date as `docker/system-baseline` in that release. Older Docker images then refuse the update and ask the user to pull the new image.
---

View File

@@ -119,15 +119,21 @@ def _run_release_script(
)
def test_base_image_uses_refreshable_tag_and_apt_does_not_upgrade_in_place() -> None:
def test_base_image_uses_refreshable_tag_and_upgrades_only_in_refresh_layer() -> None:
"""
使用官方基础镜像及所需运行依赖,不追加系统包扫描补丁。
使用官方基础镜像及所需运行依赖,不追加针对扫描结果的逐包补丁;
系统包只在 final 末尾由刷新键控制的系统安全更新层统一升级。
"""
dockerfile = DOCKERFILE.read_text(encoding="utf-8")
assert 'ARG MOVIEPILOT_PYTHON_VERSION="3.14.7"' in dockerfile
assert "FROM python:${MOVIEPILOT_PYTHON_VERSION}-slim-trixie AS base" in dockerfile
assert 'uv python install --no-bin "${MOVIEPILOT_PYTHON_VERSION}t"' in dockerfile
assert "apt-get upgrade" not in dockerfile
assert dockerfile.count("upgrade;") == 1
assert "dist-upgrade" not in dockerfile
refresh_layer = dockerfile.split('ARG MOVIEPILOT_SYSTEM_REFRESH_ID=""', maxsplit=1)[1]
assert "upgrade;" in refresh_layer
assert "apt-get install" not in refresh_layer
assert "pip uninstall" not in dockerfile
assert "break-system-packages" not in dockerfile
browser_install = dockerfile.split("python -m playwright install-deps chromium", maxsplit=1)[1]