From 2027dfcb3dd133f818366a2eecd345bb402b8ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?OAC=20Core=20=E5=BC=80=E5=8F=91=E5=B7=A5=E7=A8=8B=E5=B8=88?= Date: Sat, 3 Oct 2026 16:13:47 +0800 Subject: [PATCH 1/2] fix(release): render compose images from the release repository The release template hardcoded the upstream ghcr.io/minimax-ai namespace, so a fork release published images to ghcr.io//openagentcore but shipped a compose.yaml that pulled upstream. Render the OAC_IMAGE_* defaults from the repository the release actually published to and the tag it used. A stable publication still moves latest, so it keeps the floating tag upstream always shipped; every other release (prerelease or manual build- draft) renders the exact tag it published. Callers that omit the new values keep the upstream latest defaults, so local trials and smoke tests are unchanged. Tests cover the fork draft, the stable upstream default, and value validation. Co-authored-by: multica-agent --- deploy/compose/compose.yaml | 10 ++++---- deploy/compose/test_compose.py | 34 ++++++++++++++++++++++++++++ docs/maintainers.md | 2 +- docs/zh/maintainers.md | 2 +- scripts/publish-core-release.py | 6 +++++ scripts/publish-core-release.test.py | 13 +++++++++++ scripts/render-compose.py | 23 +++++++++++++++---- 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/deploy/compose/compose.yaml b/deploy/compose/compose.yaml index a640d5f2..79d1ce9c 100644 --- a/deploy/compose/compose.yaml +++ b/deploy/compose/compose.yaml @@ -1,11 +1,11 @@ # Release template. scripts/render-compose.py fills the __OAC_*__ tokens with this -# release's source revision and node-metadata checksum. Images default to the -# floating latest tags; set OAC_IMAGE_CORE, OAC_IMAGE_WEB or OAC_IMAGE_INGRESS +# release's source revision, node-metadata checksum, and the image repository and +# tag the release published. Set OAC_IMAGE_CORE, OAC_IMAGE_WEB or OAC_IMAGE_INGRESS # to select another reference. Do not run this file until it has been rendered. # Data is bind-mounted from ${OAC_DATA_DIR:-./data}. Set OAC_PUBLIC_URL when the # platform domain is ready; startup defaults to localhost. Other process # settings pass through unchanged; Core owns their defaults. -x-ingress-image: &ingress-image ${OAC_IMAGE_INGRESS:-ghcr.io/minimax-ai/openagentcore/ingress:latest} +x-ingress-image: &ingress-image ${OAC_IMAGE_INGRESS:-__OAC_IMAGE_REPOSITORY__/ingress:__OAC_IMAGE_TAG__} services: init: image: *ingress-image @@ -47,7 +47,7 @@ services: retries: 30 core: - image: ${OAC_IMAGE_CORE:-ghcr.io/minimax-ai/openagentcore/core:latest} + image: ${OAC_IMAGE_CORE:-__OAC_IMAGE_REPOSITORY__/core:__OAC_IMAGE_TAG__} platform: linux/amd64 user: "65532:65532" restart: unless-stopped @@ -90,7 +90,7 @@ services: target: /state web: - image: ${OAC_IMAGE_WEB:-ghcr.io/minimax-ai/openagentcore/web:latest} + image: ${OAC_IMAGE_WEB:-__OAC_IMAGE_REPOSITORY__/web:__OAC_IMAGE_TAG__} platform: linux/amd64 user: "65532:65532" restart: unless-stopped diff --git a/deploy/compose/test_compose.py b/deploy/compose/test_compose.py index 103630ca..60bc7658 100644 --- a/deploy/compose/test_compose.py +++ b/deploy/compose/test_compose.py @@ -111,5 +111,39 @@ def test_platform_network_injection_keeps_the_file_valid(self): input=json.dumps(transformed), text=True, check=True) +class RenderImageReferenceTests(unittest.TestCase): + """Rendered image defaults must match the repository and tag a release published.""" + + values = { + 'REVISION': 'd' * 40, + 'RELEASE_BASE': 'https://example.com/releases/v1/', + 'ARCHIVE_CHECKSUM': 'e' * 64, + } + + def rendered(self, **extra): + return render_compose.render({**self.values, **extra}) + + def test_defaults_keep_the_upstream_latest_images(self): + text = self.rendered() + for name in ('core', 'web', 'ingress'): + self.assertIn('${OAC_IMAGE_' + name.upper() + ':-ghcr.io/minimax-ai/openagentcore/' + name + ':latest}', text) + + def test_fork_repository_and_release_tag_replace_the_defaults(self): + tag = 'build-' + 'a' * 40 + text = self.rendered(IMAGE_REPOSITORY='ghcr.io/sunyalou/openagentcore', IMAGE_TAG=tag) + for name in ('core', 'web', 'ingress'): + self.assertIn('${OAC_IMAGE_' + name.upper() + ':-ghcr.io/sunyalou/openagentcore/' + name + ':' + tag + '}', text) + + def test_invalid_image_repository_or_tag_is_refused(self): + for repository in ('', 'ghcr.io', 'ghcr.io/Fork/Repo', 'ghcr.io/fork/repo:tag'): + with self.subTest(repository=repository): + with self.assertRaisesRegex(ValueError, 'IMAGE_REPOSITORY'): + self.rendered(IMAGE_REPOSITORY=repository) + for tag in ('', ':bad', 'has space', 'a' * 129): + with self.subTest(tag=tag): + with self.assertRaisesRegex(ValueError, 'IMAGE_TAG'): + self.rendered(IMAGE_TAG=tag) + + if __name__ == '__main__': unittest.main() diff --git a/docs/maintainers.md b/docs/maintainers.md index e6cdaa05..da8c07d3 100644 --- a/docs/maintainers.md +++ b/docs/maintainers.md @@ -130,7 +130,7 @@ Distribution and Runtime archives use `pigz` level 6 with at most four compressi ### Container registry -Version releases and manual `build-` drafts publish Linux amd64 images as `ghcr.io/minimax-ai/openagentcore/:`, where `` is `core`, `web`, `runtime` or `ingress`. For example, `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`. A draft uses the tag `build-`. PostgreSQL uses its upstream image and is not republished. The registry images are loaded from the release archives without rebuilding. Existing version tags are reused only when their image config digest matches the release; a different image stops publication. A stable release also moves each component's `latest` tag to that image. Prereleases and drafts leave `latest` unchanged. SemVer build metadata uses `_` in place of `+` in container tags; version strings longer than 128 characters cannot be published to GHCR. After the images are verified, the publisher uploads `compose.yaml` and `ports.yaml`, with checksums, rendered for that release. A draft Release stays unpublished. +Version releases and manual `build-` drafts publish Linux amd64 images to GHCR under the release repository as `ghcr.io//openagentcore/:`, where `` is `core`, `web`, `runtime` or `ingress`. For example, the upstream repository publishes `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`. A draft uses the tag `build-`. PostgreSQL uses its upstream image and is not republished. The registry images are loaded from the release archives without rebuilding. Existing version tags are reused only when their image config digest matches the release; a different image stops publication. A stable release also moves each component's `latest` tag to that image. Prereleases and drafts leave `latest` unchanged. SemVer build metadata uses `_` in place of `+` in container tags; version strings longer than 128 characters cannot be published to GHCR. After the images are verified, the publisher uploads `compose.yaml` and `ports.yaml`, with checksums, rendered for that release; each `OAC_IMAGE_{CORE,WEB,INGRESS}` default names that release's repository and the tag it published, so a fork install needs no image override. A draft Release stays unpublished. 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 c2f18137..bc748dc6 100644 --- a/docs/zh/maintainers.md +++ b/docs/zh/maintainers.md @@ -132,7 +132,7 @@ git push origin v1.2.3 ### 容器注册表 {#container-registry} -版本发布和手动的 `build-` 草稿都会将 Linux amd64 镜像发布为 `ghcr.io/minimax-ai/openagentcore/:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。例如,`ghcr.io/minimax-ai/openagentcore/core:v1.2.3`。草稿使用标签 `build-`。PostgreSQL 使用其上游镜像,不会重新发布。注册表镜像从发布归档中加载,不会重新构建。仅当现有版本标签的镜像配置摘要与本次发布相同时才复用该标签;如果镜像不同,则停止发布。稳定版还会把每个组件的 `latest` 标签移到该镜像。预发布和草稿不会改动 `latest`。SemVer 构建元数据在容器标签中使用 `_` 代替 `+`;长度超过 128 个字符的版本字符串无法发布到 GHCR。镜像验证之后,发布器会上传为该发行版渲染的 `compose.yaml` 和 `ports.yaml` 及其校验和。草稿 Release 保持未发布。 +版本发布和手动的 `build-` 草稿都会将 Linux amd64 镜像发布到发布仓库在 GHCR 下的命名空间:`ghcr.io//openagentcore/:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。例如上游仓库会发布 `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`。草稿使用标签 `build-`。PostgreSQL 使用其上游镜像,不会重新发布。注册表镜像从发布归档中加载,不会重新构建。仅当现有版本标签的镜像配置摘要与本次发布相同时才复用该标签;如果镜像不同,则停止发布。稳定版还会把每个组件的 `latest` 标签移到该镜像。预发布和草稿不会改动 `latest`。SemVer 构建元数据在容器标签中使用 `_` 代替 `+`;长度超过 128 个字符的版本字符串无法发布到 GHCR。镜像验证之后,发布器会上传为该发行版渲染的 `compose.yaml` 和 `ports.yaml` 及其校验和;每个 `OAC_IMAGE_{CORE,WEB,INGRESS}` 默认值都指向该发行版所属仓库及其发布的标签,因此 fork 安装无需覆盖镜像。草稿 Release 保持未发布。 合并的构建/发布作业使用具有 `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/publish-core-release.py b/scripts/publish-core-release.py index 9ef65467..3d7aeec8 100644 --- a/scripts/publish-core-release.py +++ b/scripts/publish-core-release.py @@ -272,10 +272,16 @@ def upload(path): raise ValueError("Release asset inventory differs from the build") stable = re.fullmatch(r"v[0-9]+\.[0-9]+\.[0-9]+(?:\+[0-9A-Za-z.-]+)?", tag) is not None images = publish_images(assets, repository, revision, tag, floating_latest=mode == "publish" and stable) + # A stable publication also moves each component's `latest` tag, which keeps the + # rendered default that upstream always shipped. Every other release renders the + # exact tag it published (for example a manual `build-` draft). + image_tag = "latest" if mode == "publish" and stable else tag.replace("+", "_") compose_files = render_compose.write_assets(assets, { "REVISION": revision, "RELEASE_BASE": "https://github.com/" + repository + "/releases/download/" + tag + "/", "ARCHIVE_CHECKSUM": distribution.sha256(assets / (stem + ".tar.gz")), + "IMAGE_REPOSITORY": "ghcr.io/" + repository.lower(), + "IMAGE_TAG": image_tag, }) 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 dd5d736e..d724a2f3 100644 --- a/scripts/publish-core-release.test.py +++ b/scripts/publish-core-release.test.py @@ -202,6 +202,19 @@ def test_manual_draft_does_not_publish(self): self.assertTrue(self.release["draft"]) self.assertFalse(any(c.args[1].startswith("git/") for c in self.api.call_args_list)) + def test_rendered_compose_uses_the_release_repository_and_tag(self): + self.canonical_repository = "sunyalou/OpenAgentCore" + self.publish(tag="build-" + self.revision, mode="draft") + text = (self.assets / "compose.yaml").read_text() + for name in ("core", "web", "ingress"): + self.assertIn("ghcr.io/sunyalou/openagentcore/" + name + ":build-" + self.revision, text) + + def test_stable_release_renders_the_upstream_latest_default(self): + self.publish() + text = (self.assets / "compose.yaml").read_text() + for name in ("core", "web", "ingress"): + self.assertIn("ghcr.io/minimax-ai/openagentcore/" + name + ":latest", text) + def test_existing_public_or_draft_release_is_refused(self): for draft in (True, False): with self.subTest(draft=draft): diff --git a/scripts/render-compose.py b/scripts/render-compose.py index 50044cbb..2118a3dd 100644 --- a/scripts/render-compose.py +++ b/scripts/render-compose.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 """Fill the Compose template with one release's node metadata. -The template is deploy/compose/compose.yaml. Images stay on their default -latest tags. A release publishes the rendered file; this script does not run Docker. +The template is deploy/compose/compose.yaml. The rendered image references default +to the release repository and tag; callers that omit IMAGE_REPOSITORY and +IMAGE_TAG keep the upstream latest tags. A release publishes the rendered file; +this script does not run Docker. """ import hashlib import pathlib @@ -13,6 +15,10 @@ TEMPLATE = ROOT / "deploy/compose/compose.yaml" PORTS = ROOT / "deploy/compose/ports.yaml" TOKENS = ("REVISION", "RELEASE_BASE", "ARCHIVE_CHECKSUM") +DEFAULT_IMAGE_REPOSITORY = "ghcr.io/minimax-ai/openagentcore" +DEFAULT_IMAGE_TAG = "latest" +IMAGE_REPOSITORY = re.compile(r"[a-z0-9]+(?:[.-][a-z0-9]+)*(?::[0-9]+)?(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)+") +IMAGE_TAG = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}") def render(values): @@ -27,12 +33,21 @@ def render(values): base = values["RELEASE_BASE"] if not base.startswith("https://") or not base.endswith("/") or " " in base: raise ValueError("RELEASE_BASE must be an https URL ending with /") + repository = values.get("IMAGE_REPOSITORY", DEFAULT_IMAGE_REPOSITORY) + tag = values.get("IMAGE_TAG", DEFAULT_IMAGE_TAG) + if not IMAGE_REPOSITORY.fullmatch(repository): + raise ValueError("IMAGE_REPOSITORY must be a lowercase registry repository") + if not IMAGE_TAG.fullmatch(tag): + raise ValueError("IMAGE_TAG must be a container tag") + replacements = {name: values[name] for name in TOKENS} + replacements["IMAGE_REPOSITORY"] = repository + replacements["IMAGE_TAG"] = tag text = TEMPLATE.read_text() - for name in TOKENS: + for name, value in replacements.items(): token = "__OAC_" + name + "__" if token not in text: raise ValueError("Compose template is missing " + token) - text = text.replace(token, values[name]) + text = text.replace(token, value) leftover = sorted(set(re.findall(r"__OAC_[A-Z_]+__", text))) if leftover: raise ValueError("Unreplaced Compose tokens: " + ", ".join(leftover)) From f5d96870e82de58e6729368121d29057cd08e2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?OAC=20Core=20=E5=BC=80=E5=8F=91=E5=B7=A5=E7=A8=8B=E5=B8=88?= Date: Sat, 3 Oct 2026 16:30:48 +0800 Subject: [PATCH 2/2] docs(release): sync Chinese translations and cover the prerelease tag The English sources changed with the compose image parameterization but both Chinese source_hash values were stale, which fails the website translation check. Recompute them after aligning the translations. install-options now says Core and Web use the release repository's tag (latest for a stable release) instead of a fixed latest image. maintainers names the image path as ghcr.io///, matching the publisher for any repository name. Add a publisher test asserting a prerelease publish renders the exact tag it pushed (not latest), alongside the stable and draft cases. Co-authored-by: multica-agent --- docs/getting-started/install-options.md | 2 +- docs/maintainers.md | 2 +- docs/zh/getting-started/install-options.md | 4 ++-- docs/zh/maintainers.md | 4 ++-- scripts/publish-core-release.test.py | 10 ++++++++++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/getting-started/install-options.md b/docs/getting-started/install-options.md index b0d0543f..c04ecb9c 100644 --- a/docs/getting-started/install-options.md +++ b/docs/getting-started/install-options.md @@ -14,7 +14,7 @@ With the one-line command, append them after `bash -s --`. `--version TAG` selec ## Docker Compose and hosting platforms -Use the `compose.yaml` from a release with Docker Compose 2.26 or newer on Linux amd64. The release renders node metadata into 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 is bind-mounted from a directory. 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 with Docker Compose 2.26 or newer on Linux amd64. The release renders node metadata into the [Compose template](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml). Core and Web use images from the release repository at the tag that release published, which is `latest` for a stable release, and PostgreSQL uses `postgres:16-alpine`. It starts PostgreSQL, Core and Web. Web forwards `/v1` and `/api/v1` to Core. Data is bind-mounted from a directory. 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` and `ports.yaml` from the same release into one directory, then run: diff --git a/docs/maintainers.md b/docs/maintainers.md index da8c07d3..1d5c8522 100644 --- a/docs/maintainers.md +++ b/docs/maintainers.md @@ -130,7 +130,7 @@ Distribution and Runtime archives use `pigz` level 6 with at most four compressi ### Container registry -Version releases and manual `build-` drafts publish Linux amd64 images to GHCR under the release repository as `ghcr.io//openagentcore/:`, where `` is `core`, `web`, `runtime` or `ingress`. For example, the upstream repository publishes `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`. A draft uses the tag `build-`. PostgreSQL uses its upstream image and is not republished. The registry images are loaded from the release archives without rebuilding. Existing version tags are reused only when their image config digest matches the release; a different image stops publication. A stable release also moves each component's `latest` tag to that image. Prereleases and drafts leave `latest` unchanged. SemVer build metadata uses `_` in place of `+` in container tags; version strings longer than 128 characters cannot be published to GHCR. After the images are verified, the publisher uploads `compose.yaml` and `ports.yaml`, with checksums, rendered for that release; each `OAC_IMAGE_{CORE,WEB,INGRESS}` default names that release's repository and the tag it published, so a fork install needs no image override. A draft Release stays unpublished. +Version releases and manual `build-` drafts publish Linux amd64 images to GHCR under the release repository as `ghcr.io///:`, where `` is `core`, `web`, `runtime` or `ingress`. For example, the upstream repository publishes `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`. A draft uses the tag `build-`. PostgreSQL uses its upstream image and is not republished. The registry images are loaded from the release archives without rebuilding. Existing version tags are reused only when their image config digest matches the release; a different image stops publication. A stable release also moves each component's `latest` tag to that image. Prereleases and drafts leave `latest` unchanged. SemVer build metadata uses `_` in place of `+` in container tags; version strings longer than 128 characters cannot be published to GHCR. After the images are verified, the publisher uploads `compose.yaml` and `ports.yaml`, with checksums, rendered for that release; each `OAC_IMAGE_{CORE,WEB,INGRESS}` default names that release's repository and the tag it published, so a fork install needs no image override. A draft Release stays unpublished. 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/getting-started/install-options.md b/docs/zh/getting-started/install-options.md index 48931f84..97793084 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: 53468efe866d0774ec60d015eb6c168dd5ae5170f6b7af0c1505a63e32362d11 +source_hash: 4aafadb9c477e3c7518920fae3a1d75b95fd00c8e430fccf59cea3cb1515919e --- [默认安装](install.md)无需任何选项。使用本页可以在现有反向代理后运行,或者在无法访问互联网时进行安装。 @@ -18,7 +18,7 @@ source_hash: 53468efe866d0774ec60d015eb6c168dd5ae5170f6b7af0c1505a63e32362d11 ## Docker Compose 与托管平台 {#docker-compose-and-hosting-platforms} -在 Linux amd64 上使用发行版中的 `compose.yaml` 和 Docker Compose 2.26 或更高版本。发行流程会把节点元数据渲染进 [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。数据通过目录 bind mount 挂载。一次性初始化服务会在该目录中生成随机机密信息并准备节点安装程序;Core 启动时执行数据库迁移。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 +在 Linux amd64 上使用发行版中的 `compose.yaml` 和 Docker Compose 2.26 或更高版本。发行流程会把节点元数据渲染进 [Compose 模板](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/compose/compose.yaml)。Core 和 Web 使用该 release 所属仓库发布的 tag 镜像(稳定版为 `latest`),PostgreSQL 使用 `postgres:16-alpine`。它会启动 PostgreSQL、Core 和 Web。Web 把 `/v1` 和 `/api/v1` 转发到 Core。数据通过目录 bind mount 挂载。一次性初始化服务会在该目录中生成随机机密信息并准备节点安装程序;Core 启动时执行数据库迁移。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 进行本地试用时,请将同一发行版的 `compose.yaml` 和 `ports.yaml` 下载到同一个目录,然后运行: diff --git a/docs/zh/maintainers.md b/docs/zh/maintainers.md index bc748dc6..69e449cb 100644 --- a/docs/zh/maintainers.md +++ b/docs/zh/maintainers.md @@ -1,7 +1,7 @@ --- title: "构建并发布 OpenAgentCore" source: docs/maintainers.md -source_hash: ac744d8df2682f0c19eb6b05c1ef50a9e7c7a9d214316317458669f4cb00f477 +source_hash: adf81c3f9d3e7c00e941c9756def0a22f93a866a23ed287fbfd7da85caa4b348 --- 本指南面向负责构建和发布 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)。 @@ -132,7 +132,7 @@ git push origin v1.2.3 ### 容器注册表 {#container-registry} -版本发布和手动的 `build-` 草稿都会将 Linux amd64 镜像发布到发布仓库在 GHCR 下的命名空间:`ghcr.io//openagentcore/:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。例如上游仓库会发布 `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`。草稿使用标签 `build-`。PostgreSQL 使用其上游镜像,不会重新发布。注册表镜像从发布归档中加载,不会重新构建。仅当现有版本标签的镜像配置摘要与本次发布相同时才复用该标签;如果镜像不同,则停止发布。稳定版还会把每个组件的 `latest` 标签移到该镜像。预发布和草稿不会改动 `latest`。SemVer 构建元数据在容器标签中使用 `_` 代替 `+`;长度超过 128 个字符的版本字符串无法发布到 GHCR。镜像验证之后,发布器会上传为该发行版渲染的 `compose.yaml` 和 `ports.yaml` 及其校验和;每个 `OAC_IMAGE_{CORE,WEB,INGRESS}` 默认值都指向该发行版所属仓库及其发布的标签,因此 fork 安装无需覆盖镜像。草稿 Release 保持未发布。 +版本发布和手动的 `build-` 草稿都会将 Linux amd64 镜像发布到发布仓库在 GHCR 下的命名空间:`ghcr.io///:`,其中 `` 为 `core`、`web`、`runtime` 或 `ingress`。例如上游仓库会发布 `ghcr.io/minimax-ai/openagentcore/core:v1.2.3`。草稿使用标签 `build-`。PostgreSQL 使用其上游镜像,不会重新发布。注册表镜像从发布归档中加载,不会重新构建。仅当现有版本标签的镜像配置摘要与本次发布相同时才复用该标签;如果镜像不同,则停止发布。稳定版还会把每个组件的 `latest` 标签移到该镜像。预发布和草稿不会改动 `latest`。SemVer 构建元数据在容器标签中使用 `_` 代替 `+`;长度超过 128 个字符的版本字符串无法发布到 GHCR。镜像验证之后,发布器会上传为该发行版渲染的 `compose.yaml` 和 `ports.yaml` 及其校验和;每个 `OAC_IMAGE_{CORE,WEB,INGRESS}` 默认值都指向该发行版所属仓库及其发布的标签,因此 fork 安装无需覆盖镜像。草稿 Release 保持未发布。 合并的构建/发布作业使用具有 `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/publish-core-release.test.py b/scripts/publish-core-release.test.py index d724a2f3..3416d92a 100644 --- a/scripts/publish-core-release.test.py +++ b/scripts/publish-core-release.test.py @@ -215,6 +215,16 @@ def test_stable_release_renders_the_upstream_latest_default(self): for name in ("core", "web", "ingress"): self.assertIn("ghcr.io/minimax-ai/openagentcore/" + name + ":latest", text) + def test_prerelease_release_renders_the_tag_it_publishes(self): + self.publish("v1.2.3-rc.1") + self.images.assert_called_once() + self.assertEqual(self.images.call_args.args[3], "v1.2.3-rc.1") + self.assertFalse(self.images.call_args.kwargs["floating_latest"]) + text = (self.assets / "compose.yaml").read_text() + for name in ("core", "web", "ingress"): + self.assertIn("ghcr.io/minimax-ai/openagentcore/" + name + ":v1.2.3-rc.1", text) + self.assertNotIn(":latest", text) + def test_existing_public_or_draft_release_is_refused(self): for draft in (True, False): with self.subTest(draft=draft):