From edb5db2a7688731cbe9c34a4c3d1bbf2317afa47 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Mon, 12 Oct 2026 08:29:22 +0000 Subject: [PATCH 1/2] Pin release Compose images to published digests --- deploy/compose/compose.yaml | 6 +++--- deploy/compose/test_compose.py | 4 +++- deploy/install-source.py | 2 ++ deploy/test_install_source.py | 1 + docs/maintainers.md | 2 +- docs/zh/maintainers.md | 4 ++-- scripts/compose-smoke.py | 2 ++ scripts/publish-core-release.py | 2 ++ scripts/publish-core-release.test.py | 15 +++++++++++++++ scripts/render-compose.py | 9 +++++---- 10 files changed, 36 insertions(+), 11 deletions(-) diff --git a/deploy/compose/compose.yaml b/deploy/compose/compose.yaml index 70471dc31..51335df9c 100644 --- a/deploy/compose/compose.yaml +++ b/deploy/compose/compose.yaml @@ -1,5 +1,5 @@ # Release template. The publisher pins the initialization image to this release. -# Core and Web default to latest; OAC_IMAGE_* selects another reference. +# Release images are pinned; OAC_IMAGE_* selects another reference. # Docker owns data permissions on every host platform. x-ingress-image: &ingress-image ${OAC_IMAGE_INGRESS:-__OAC_INIT_IMAGE__} services: @@ -42,7 +42,7 @@ services: retries: 30 core: - image: ${OAC_IMAGE_CORE:-ghcr.io/minimax-ai/openagentcore/core:latest} + image: ${OAC_IMAGE_CORE:-__OAC_CORE_IMAGE__} user: "65532:65532" restart: unless-stopped read_only: true @@ -89,7 +89,7 @@ services: published: "${OAC_WEB_PORT:-8080}" host_ip: "${OAC_HOST:-127.0.0.1}" protocol: tcp - image: ${OAC_IMAGE_WEB:-ghcr.io/minimax-ai/openagentcore/web:latest} + image: ${OAC_IMAGE_WEB:-__OAC_WEB_IMAGE__} user: "65532:65532" restart: unless-stopped read_only: true diff --git a/deploy/compose/test_compose.py b/deploy/compose/test_compose.py index 4f14ef726..e27ae3341 100644 --- a/deploy/compose/test_compose.py +++ b/deploy/compose/test_compose.py @@ -19,6 +19,8 @@ def rendered_compose(directory): text = render_compose.render({ 'REVISION': 'd' * 40, 'INIT_IMAGE': 'ghcr.io/minimax-ai/openagentcore/ingress@sha256:' + 'e' * 64, + 'CORE_IMAGE': 'ghcr.io/minimax-ai/openagentcore/core@sha256:' + 'e' * 64, + 'WEB_IMAGE': 'ghcr.io/minimax-ai/openagentcore/web@sha256:' + 'e' * 64, }) path = Path(directory) / 'compose.yaml' path.write_text(text) @@ -58,7 +60,7 @@ def test_compose_uses_private_services_and_ordered_initialization(self): self.assertNotIn('build', service) if service is not services['web']: self.assertNotIn('ports', service) - self.assertTrue(service['image'].endswith(':latest') or service['image'] == 'postgres:16-alpine' or service['image'].endswith('@sha256:' + 'e' * 64)) + self.assertTrue(service['image'] == 'postgres:16-alpine' or service['image'].endswith('@sha256:' + 'e' * 64)) for volume in service.get('volumes', []): self.assertNotIn('docker.sock', json.dumps(volume)) self.assertEqual(volume['type'], 'volume') diff --git a/deploy/install-source.py b/deploy/install-source.py index ef1641e3e..9f92d65b0 100644 --- a/deploy/install-source.py +++ b/deploy/install-source.py @@ -42,6 +42,8 @@ def install(bundle, destination, host, port, public_url): for name in files: os.chmod(Path(root) / name, 0o644) template = (bundle / 'compose.yaml.template').read_text().replace('__OAC_REVISION__', revision).replace('__OAC_INIT_IMAGE__', images['ingress']) + for name in ('core', 'web'): + template = template.replace('__OAC_' + name.upper() + '_IMAGE__', images[name]) override = {'services': {name: {'image': images[image]} for name, image in (('core', 'core'), ('web', 'web'), ('init', 'ingress'), ('database', 'database'))}} override['services']['web']['volumes'] = [{'type': 'bind', 'source': str(payload), 'target': '/node-payload', 'read_only': True}] diff --git a/deploy/test_install_source.py b/deploy/test_install_source.py index 4bc59f735..1544d7131 100644 --- a/deploy/test_install_source.py +++ b/deploy/test_install_source.py @@ -45,6 +45,7 @@ def install(self): def test_complete_compose_preserves_secrets_and_serves_whole_payload(self): self.install() + self.assertNotIn('__OAC_', (self.destination / 'compose.yaml').read_text()) config = json.loads(subprocess.check_output(['docker', 'compose', 'config', '--format', 'json'], cwd=self.destination, text=True)) services = config['services'] for name in ('core', 'web', 'database', 'init'): diff --git a/docs/maintainers.md b/docs/maintainers.md index b33184ce3..0247fbf63 100644 --- a/docs/maintainers.md +++ b/docs/maintainers.md @@ -138,7 +138,7 @@ Distribution and Runtime archives use `pigz` level 6 with at most four compressi ### Container registry -Version releases and manual `build-` drafts publish `ghcr.io/minimax-ai/openagentcore/:`, where `` is `core`, `web`, `runtime` or `ingress`. Core, Web and ingress indexes contain Linux amd64 and arm64 images; Runtime contains Linux amd64. Platform images use `-` tags and are loaded from the release archives. Existing version tags must match the release images and platform set. The publisher verifies every version index before updating `latest` for a stable release; prereleases and drafts leave `latest` unchanged. PostgreSQL uses its upstream image. SemVer build metadata uses `_` in place of `+` in container tags; version strings are limited to 128 characters. After verifying the images, the publisher uploads the release's `compose.yaml` and checksum list. Compose pins ingress by its index digest, and initialization checks its build revision against the Compose revision. +Version releases and manual `build-` drafts publish `ghcr.io/minimax-ai/openagentcore/:`, where `` is `core`, `web`, `runtime` or `ingress`. Core, Web and ingress indexes contain Linux amd64 and arm64 images; Runtime contains Linux amd64. Platform images use `-` tags and are loaded from the release archives. Existing version tags must match the release images and platform set. The publisher verifies every version index before updating `latest` for a stable release; prereleases and drafts leave `latest` unchanged. PostgreSQL uses its upstream image. SemVer build metadata uses `_` in place of `+` in container tags; version strings are limited to 128 characters. After verifying the images, the publisher uploads the release's `compose.yaml` and checksum list. Compose pins Core, Web and ingress by their release index digests, and initialization checks its build revision against the Compose revision. The combined build/publication job uses `GITHUB_TOKEN` with `packages: write`. On the first publication, GitHub creates each container package as private: a package administrator must change all four packages to **Public** in their package settings before users can pull anonymously. See [GitHub container visibility](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). Verify an unauthenticated pull after changing visibility. Repository visibility alone does not make a new container package public. diff --git a/docs/zh/maintainers.md b/docs/zh/maintainers.md index b4c5da86d..2a01d90de 100644 --- a/docs/zh/maintainers.md +++ b/docs/zh/maintainers.md @@ -1,7 +1,7 @@ --- title: "构建并发布 OpenAgentCore" source: docs/maintainers.md -source_hash: e575ad6ef718ce7791f9b6b1ac05467a0a98923512e17b05d3ffab2e75fcfc74 +source_hash: 402a990e859cb3fb0155e2b4e60421cd841de88f84a49b678eb918450b253114 --- 本指南面向负责构建和发布 OpenAgentCore 的维护者。要安装 Core 和 Web,请使用 [安装指南](getting-started/install.md)。安装器代码遵循的规则见 [部署](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/README.md) 和 [节点安装器](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/node/README.md);必需检查见 [CONTRIBUTING](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#required-checks)。 @@ -140,7 +140,7 @@ git push origin v1.2.3 ### 容器注册表 {#container-registry} -版本发布和手动 `build-` 草稿使用 `ghcr.io/minimax-ai/openagentcore/:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。Core、Web 和 ingress 索引包含 Linux amd64 和 arm64 镜像,Runtime 包含 Linux amd64。各平台镜像使用 `-` 标签,从发行归档加载。已有版本标签必须与发行镜像及平台集合一致。发布器校验全部版本索引后,才为稳定版更新 `latest`;预发布版和草稿保持 `latest` 不变。PostgreSQL 使用上游镜像。容器标签中的 SemVer 构建元数据用 `_` 替换 `+`,版本字符串上限为 128 个字符。镜像校验后,发布器上传该版本的 `compose.yaml` 和校验和清单。Compose 用索引摘要固定 ingress,初始化时检查其构建版本与 Compose 版本一致。 +版本发布和手动 `build-` 草稿使用 `ghcr.io/minimax-ai/openagentcore/:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。Core、Web 和 ingress 索引包含 Linux amd64 和 arm64 镜像,Runtime 包含 Linux amd64。各平台镜像使用 `-` 标签,从发行归档加载。已有版本标签必须与发行镜像及平台集合一致。发布器校验全部版本索引后,才为稳定版更新 `latest`;预发布版和草稿保持 `latest` 不变。PostgreSQL 使用上游镜像。容器标签中的 SemVer 构建元数据用 `_` 替换 `+`,版本字符串上限为 128 个字符。镜像校验后,发布器上传该版本的 `compose.yaml` 和校验和清单。Compose 用对应发行版的索引摘要固定 Core、Web 和 ingress,初始化时检查其构建版本与 Compose 版本一致。 合并的构建/发布作业使用具有 `packages: write` 权限的 `GITHUB_TOKEN`。首次发布时,GitHub 会将每个容器软件包创建为私有:软件包管理员必须先在各自的软件包设置中将全部四个软件包改为 **Public**,用户才能匿名拉取。请参阅 [GitHub container visibility](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)。更改可见性后,请验证未认证拉取。仅更改仓库可见性并不会使新的容器软件包变为公开。 diff --git a/scripts/compose-smoke.py b/scripts/compose-smoke.py index f2c4c9946..cd402ba79 100644 --- a/scripts/compose-smoke.py +++ b/scripts/compose-smoke.py @@ -108,6 +108,8 @@ def main(): rendered.write_text(render_compose.render({ 'REVISION': pins['revision'], 'INIT_IMAGE': 'ghcr.io/minimax-ai/openagentcore/ingress@sha256:' + '0' * 64, + 'CORE_IMAGE': 'ghcr.io/minimax-ai/openagentcore/core@sha256:' + '0' * 64, + 'WEB_IMAGE': 'ghcr.io/minimax-ai/openagentcore/web@sha256:' + '0' * 64, })) override = directory / 'offline-init.json' images = build_images(directory, project.removeprefix('oac-smoke-')) diff --git a/scripts/publish-core-release.py b/scripts/publish-core-release.py index dcbec0aa3..0730bf7bc 100644 --- a/scripts/publish-core-release.py +++ b/scripts/publish-core-release.py @@ -302,6 +302,8 @@ def upload(path): compose_files = render_compose.write_assets(assets, { "REVISION": revision, "INIT_IMAGE": images["ingress"]["digest"], + "CORE_IMAGE": images["core"]["digest"], + "WEB_IMAGE": images["web"]["digest"], }) expected.update({path.name: path.stat().st_size for path in compose_files}) parallel_each(upload, compose_files) diff --git a/scripts/publish-core-release.test.py b/scripts/publish-core-release.test.py index a73276916..7ec9ea4e9 100644 --- a/scripts/publish-core-release.test.py +++ b/scripts/publish-core-release.test.py @@ -158,6 +158,21 @@ def test_version_tag_publishes_complete_fixed_id(self): self.assertEqual(self.api.call_args.args[1:], ("releases/7", "--method", "PATCH", "-F", "draft=false")) + def test_compose_defaults_use_published_images_for_every_release_mode(self): + for tag, mode in (("v1.2.3", "publish"), ("v1.2.3-rc.1", "publish"), + ("build-" + self.revision, "draft")): + with self.subTest(tag=tag): + # Each publication starts with the original build assets. + for name in ("compose.yaml", "compose-sha256sums.txt"): + (self.assets / name).unlink(missing_ok=True) + self.publish(tag=tag, mode=mode) + compose = (self.assets / "compose.yaml").read_text() + for name in ("core", "web", "ingress"): + self.assertIn("${OAC_IMAGE_" + name.upper() + ":-" + + self.images.return_value[name]["digest"] + "}", compose) + self.assertNotIn(":latest", compose) + self.assertNotIn("__OAC_", compose) + def test_repository_rename_uses_current_identity_before_writes(self): self.context_repository = "MiniMax-AI/previous-name" self.publish() diff --git a/scripts/render-compose.py b/scripts/render-compose.py index cc88c4f28..b4e00c248 100644 --- a/scripts/render-compose.py +++ b/scripts/render-compose.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Fill the Compose template with one release's node metadata. -The template is deploy/compose/compose.yaml. The initialization image is pinned; Core and Web default to latest. A release publishes the rendered file; this script does not run Docker. +The template is deploy/compose/compose.yaml. Initialization, Core and Web images are pinned to the release. A release publishes the rendered file; this script does not run Docker. """ import hashlib import pathlib @@ -10,7 +10,7 @@ ROOT = pathlib.Path(__file__).resolve().parents[1] TEMPLATE = ROOT / "deploy/compose/compose.yaml" -TOKENS = ("REVISION", "INIT_IMAGE") +TOKENS = ("REVISION", "INIT_IMAGE", "CORE_IMAGE", "WEB_IMAGE") def render(values): @@ -20,8 +20,9 @@ def render(values): raise ValueError("Missing Compose values: " + ", ".join(missing)) if not re.fullmatch(r"[0-9a-f]{40}", values["REVISION"]): raise ValueError("REVISION must be a full source commit SHA") - if not re.fullmatch(r"ghcr\.io/[a-z0-9._/-]+@sha256:[0-9a-f]{64}", values["INIT_IMAGE"]): - raise ValueError("INIT_IMAGE must be an immutable GHCR image reference") + for name in TOKENS[1:]: + if not re.fullmatch(r"ghcr\.io/[a-z0-9._/-]+@sha256:[0-9a-f]{64}", values[name]): + raise ValueError(name + " must be an immutable GHCR image reference") text = TEMPLATE.read_text() for name in TOKENS: token = "__OAC_" + name + "__" From cde104608943212fa6354a53cc166e650bf7d0b1 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Mon, 12 Oct 2026 08:30:47 +0000 Subject: [PATCH 2/2] Describe release pinned Compose images in install options --- docs/getting-started/install-options.md | 2 +- docs/zh/getting-started/install-options.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting-started/install-options.md b/docs/getting-started/install-options.md index 074c37754..6e96148dc 100644 --- a/docs/getting-started/install-options.md +++ b/docs/getting-started/install-options.md @@ -14,7 +14,7 @@ On Windows, download `install.ps1` and pass the same flags with `& ./install.ps1 ## Docker Compose and hosting platforms -Use the `compose.yaml` from a release on any [supported Core host](./install.md#prerequisites). The release pins its initialization image and source revision in the [Compose template](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml). Core and Web use the `latest` images, and PostgreSQL uses `postgres:16-alpine`. It starts PostgreSQL, Core and Web. Web forwards `/v1` and `/api/v1` to Core. Data lives in a named Docker volume. The one-time initialization service generates random secrets there and prepares the node installer; Core applies database migrations when it starts. [Compose configuration](../configuration.md#compose-installations) owns the settings and the data directory. +Use the `compose.yaml` from a release on any [supported Core host](./install.md#prerequisites). The release pins its initialization, Core and Web images and source revision in the [Compose template](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml). PostgreSQL uses `postgres:16-alpine`. It starts PostgreSQL, Core and Web. Web forwards `/v1` and `/api/v1` to Core. Data lives in a named Docker volume. The one-time initialization service generates random secrets there and prepares the node installer; Core applies database migrations when it starts. [Compose configuration](../configuration.md#compose-installations) owns the settings and the data directory. For a local trial, download `compose.yaml` from a release into an empty directory, then run: diff --git a/docs/zh/getting-started/install-options.md b/docs/zh/getting-started/install-options.md index a4254a418..c2f6c1b7c 100644 --- a/docs/zh/getting-started/install-options.md +++ b/docs/zh/getting-started/install-options.md @@ -1,7 +1,7 @@ --- title: "安装选项" source: docs/getting-started/install-options.md -source_hash: 698909d30bf0ebdff2656deb6a6cb00c18181e835e2c1a7972de0d1e7ca47733 +source_hash: 7423324720152cc6c58459f3e60b3cb61f5c97ae291ff33867f1e90172831e91 --- [默认安装](install.md)无需任何选项。本页介绍安装选项、Compose 部署和反向代理配置。 @@ -16,7 +16,7 @@ Windows 可下载 `install.ps1`,然后使用相同参数,例如 `& ./install ## Docker Compose 与托管平台 {#docker-compose-and-hosting-platforms} -在任一[支持的 Core 主机](install.md#prerequisites)上使用发行版中的 `compose.yaml`。发行流程会在 [Compose 模板](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml)中固定初始化镜像及源码版本。Core 和 Web 使用 `latest` 镜像,PostgreSQL 使用 `postgres:16-alpine`。它会启动 PostgreSQL、Core 和 Web。Web 把 `/v1` 和 `/api/v1` 转发到 Core。数据保存在 Docker 命名卷中。一次性初始化服务会在该目录中生成随机机密信息并准备节点安装程序;Core 启动时执行数据库迁移。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 +在任一[支持的 Core 主机](install.md#prerequisites)上使用发行版中的 `compose.yaml`。发行流程会在 [Compose 模板](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml)中固定初始化、Core 和 Web 镜像及源码版本。PostgreSQL 使用 `postgres:16-alpine`。它会启动 PostgreSQL、Core 和 Web。Web 把 `/v1` 和 `/api/v1` 转发到 Core。数据保存在 Docker 命名卷中。一次性初始化服务会在该目录中生成随机机密信息并准备节点安装程序;Core 启动时执行数据库迁移。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 进行本地试用时,请将发行版的 `compose.yaml` 下载到一个空目录,然后运行: