11#!/usr/bin/env python3
2- """Build VM boot assets using Podman.
2+ """Build VM boot assets using Podman/Docker .
33
44Extracts vmlinuz + initrd from Debian ARM64, builds a squashfs rootfs
55(zstd-compressed) with developer tools and AI CLIs pre-installed.
66Output goes to ../assets/.
77"""
88
9- import hashlib
9+ import os
1010import shutil
1111import subprocess
1212import sys
2222# Use podman, fall back to docker
2323RUNTIME = "podman" if shutil .which ("podman" ) else "docker"
2424
25+ # In GitHub Actions with docker, use buildx + GHA cache for faster rebuilds.
26+ CI = bool (os .environ .get ("GITHUB_ACTIONS" ))
27+
2528
2629def run (cmd : list [str ], ** kwargs ) -> subprocess .CompletedProcess :
2730 print (f" -> { ' ' .join (cmd )} " )
2831 return subprocess .run (cmd , check = True , ** kwargs )
2932
3033
34+ def _docker_build (tag : str , dockerfile : str , context : str ):
35+ """Build a container image, using BuildKit GHA cache in CI."""
36+ if CI and RUNTIME == "docker" :
37+ run ([
38+ "docker" , "buildx" , "build" ,
39+ "--platform" , "linux/arm64" ,
40+ "--cache-from" , "type=gha,scope=" + tag ,
41+ "--cache-to" , "type=gha,mode=max,scope=" + tag ,
42+ "--load" ,
43+ "-t" , tag ,
44+ "-f" , dockerfile ,
45+ context ,
46+ ])
47+ else :
48+ run ([
49+ RUNTIME , "build" ,
50+ "--platform" , "linux/arm64" ,
51+ "-t" , tag ,
52+ "-f" , dockerfile ,
53+ context ,
54+ ])
55+
56+
3157def build_kernel_image ():
3258 """Build the container image that extracts kernel + initrd."""
3359 print (f"Building kernel extraction image with { RUNTIME } ..." )
34- run ([
35- RUNTIME , "build" ,
36- "--platform" , "linux/arm64" ,
37- "-t" , IMAGE_TAG ,
38- "-f" , str (SCRIPT_DIR / "Dockerfile.kernel" ),
39- str (SCRIPT_DIR ),
40- ])
60+ _docker_build (IMAGE_TAG , str (SCRIPT_DIR / "Dockerfile.kernel" ), str (SCRIPT_DIR ))
4161
4262
4363def extract_assets ():
@@ -87,12 +107,11 @@ def build_agent():
87107 ], cwd = str (REPO_ROOT ))
88108
89109 # Copy binaries to images/ so Dockerfile.rootfs can COPY them.
90- import shutil as _shutil
91110 release_dir = REPO_ROOT / "target" / "aarch64-unknown-linux-musl" / "release"
92111 for binary_name in ["capsem-pty-agent" , "capsem-net-proxy" ]:
93112 src = release_dir / binary_name
94113 dst = SCRIPT_DIR / binary_name
95- _shutil .copy2 (str (src ), str (dst ))
114+ shutil .copy2 (str (src ), str (dst ))
96115 print (f" { binary_name } : { dst } ({ dst .stat ().st_size } bytes)" )
97116
98117
@@ -107,13 +126,7 @@ def create_rootfs():
107126 print (f" capsem-ca.crt: { ca_dst } " )
108127
109128 # 1. Build rootfs container (arm64 binaries)
110- run ([
111- RUNTIME , "build" ,
112- "--platform" , "linux/arm64" ,
113- "-t" , ROOTFS_IMAGE_TAG ,
114- "-f" , str (SCRIPT_DIR / "Dockerfile.rootfs" ),
115- str (SCRIPT_DIR ),
116- ])
129+ _docker_build (ROOTFS_IMAGE_TAG , str (SCRIPT_DIR / "Dockerfile.rootfs" ), str (SCRIPT_DIR ))
117130
118131 # 2. Export container filesystem as tar
119132 print ("Exporting rootfs filesystem..." )
@@ -129,7 +142,7 @@ def create_rootfs():
129142 finally :
130143 run ([RUNTIME , "rm" , cid ])
131144
132- # 3. Create squashfs image from tar (zstd level 19 for best compression)
145+ # 3. Create squashfs image from tar (zstd level 15 for good compression)
133146 print ("Creating squashfs rootfs image (zstd compression)..." )
134147 abs_assets = str (ASSETS_DIR .resolve ())
135148 run ([
@@ -138,19 +151,19 @@ def create_rootfs():
138151 "debian:bookworm-slim" , "bash" , "-c" ,
139152 "apt-get update && apt-get install -y squashfs-tools zstd && "
140153 "mkdir /rootfs && tar xf /assets/rootfs.tar -C /rootfs && "
141- "mksquashfs /rootfs /assets/rootfs.img -comp zstd -Xcompression-level 15 -b 64K -noappend" ,
154+ "mksquashfs /rootfs /assets/rootfs.squashfs -comp zstd -Xcompression-level 15 -b 64K -noappend" ,
142155 ])
143156
144157 # 4. Cleanup tar
145158 tar_path .unlink ()
146159
147- img_path = ASSETS_DIR / "rootfs.img "
148- print (f" rootfs.img : { img_path } ({ img_path .stat ().st_size // (1024 * 1024 )} MB)" )
160+ img_path = ASSETS_DIR / "rootfs.squashfs "
161+ print (f" rootfs.squashfs : { img_path } ({ img_path .stat ().st_size // (1024 * 1024 )} MB)" )
149162
150163
151164def generate_checksums ():
152165 print ("Generating BLAKE3 checksums..." )
153- files = [f for f in ["vmlinuz" , "initrd.img" , "rootfs.img " ]
166+ files = [f for f in ["vmlinuz" , "initrd.img" , "rootfs.squashfs " ]
154167 if (ASSETS_DIR / f ).exists ()]
155168 result = subprocess .run (
156169 ["b3sum" ] + files ,
@@ -166,6 +179,8 @@ def generate_checksums():
166179
167180def main ():
168181 print (f"Using container runtime: { RUNTIME } " )
182+ if CI :
183+ print (" CI mode: Docker BuildKit GHA cache enabled" )
169184 build_kernel_image ()
170185 extract_assets ()
171186 build_agent ()
0 commit comments