1010import time
1111import urllib .request
1212from dataclasses import dataclass
13+ from hashlib import sha256
1314from pathlib import Path
1415from typing import Any
1516
1617from agentrun_cli ._utils .agentruntime_yaml import ParsedAgentRuntime , ParsedCloudBuild
1718
18- BUILDER_RELEASE_TAG = "v0.0.0-20260527-022927-3f8907ca6b2f "
19+ BUILDER_RELEASE_TAG = "latest "
1920BUILDER_BASE_URL = "https://images.devsapp.cn/docker-image-builder"
2021
2122
@@ -206,14 +207,17 @@ def ensure_builder_binary() -> str:
206207 tag = os .getenv ("DOCKER_IMAGE_BUILDER_BINTAG" , "" ).strip () or BUILDER_RELEASE_TAG
207208 install_dir = Path .home () / ".docker-image-builder" / tag
208209 target = install_dir / _executable_name ()
209- if _is_executable (target ):
210- return str (target )
211210
212211 install_dir .mkdir (parents = True , exist_ok = True )
213212 tmp = install_dir / f"{ _executable_name ()} .tmp-{ os .getpid ()} "
214- url = f"{ BUILDER_BASE_URL } /{ tag } /{ _artifact_name ()} "
213+ artifact = _artifact_name ()
214+ url = f"{ BUILDER_BASE_URL } /{ tag } /{ artifact } "
215215 try :
216+ expected_sha256 = _download_sha256 (f"{ url } .sha256" , artifact )
217+ if _is_executable (target ) and _sha256_file (target ) == expected_sha256 :
218+ return str (target )
216219 _download_binary (url , tmp )
220+ _verify_sha256 (tmp , expected_sha256 )
217221 tmp .chmod (tmp .stat ().st_mode | stat .S_IXUSR | stat .S_IXGRP | stat .S_IXOTH )
218222 tmp .replace (target )
219223 except Exception as exc :
@@ -233,6 +237,66 @@ def _download_binary(url: str, target: Path) -> None:
233237 target .write_bytes (resp .read ())
234238
235239
240+ def _download_sha256 (url : str , artifact_name : str ) -> str :
241+ """Download and parse a SHA256 checksum file.
242+
243+ Args:
244+ url: Checksum URL.
245+ artifact_name: Expected release artifact name.
246+ """
247+ with urllib .request .urlopen (url , timeout = 30 ) as resp : # noqa: S310
248+ text = resp .read ().decode ("utf-8" )
249+ return _parse_sha256 (text , artifact_name )
250+
251+
252+ def _parse_sha256 (text : str , artifact_name : str ) -> str :
253+ """Parse a SHA256 checksum file.
254+
255+ Args:
256+ text: Checksum file content.
257+ artifact_name: Expected release artifact name.
258+ """
259+ for raw_line in text .splitlines ():
260+ line = raw_line .strip ()
261+ if not line or line .startswith ("#" ):
262+ continue
263+ parts = line .split ()
264+ digest = parts [0 ].lower ()
265+ if len (digest ) != 64 or any (ch not in "0123456789abcdef" for ch in digest ):
266+ continue
267+ if len (parts ) == 1 or parts [- 1 ].lstrip ("*" ) == artifact_name :
268+ return digest
269+ raise CloudBuildError (f"invalid sha256 checksum file for { artifact_name } " )
270+
271+
272+ def _verify_sha256 (path : Path , expected_sha256 : str ) -> None :
273+ """Verify a local file against an expected SHA256 digest.
274+
275+ Args:
276+ path: File path to verify.
277+ expected_sha256: Expected SHA256 digest.
278+ """
279+ actual_sha256 = _sha256_file (path )
280+ if actual_sha256 != expected_sha256 :
281+ raise CloudBuildError (
282+ "checksum mismatch for docker-image-builder: "
283+ f"expected { expected_sha256 } , got { actual_sha256 } "
284+ )
285+
286+
287+ def _sha256_file (path : Path ) -> str :
288+ """Compute the SHA256 digest of a local file.
289+
290+ Args:
291+ path: File path to hash.
292+ """
293+ digest = sha256 ()
294+ with path .open ("rb" ) as f :
295+ for chunk in iter (lambda : f .read (1024 * 1024 ), b"" ):
296+ digest .update (chunk )
297+ return digest .hexdigest ()
298+
299+
236300def _is_executable (path : Path ) -> bool :
237301 """Return whether the path is an executable file.
238302
0 commit comments