""" 构建并上传 forge 编译环境镜像 默认仓库是 docker.pchuan.top/forge 1. 读取目标容器配置 2. 检查目标 Dockerfile 3. 根据目标目录源码时间生成 SOURCE_VERSION 4. 执行 docker build 并打上目标 tag 5. 默认执行 docker push 上传镜像 - target 支持 ubuntu 和 bun - --no-push 只构建不上传 - --force-build 使用 docker build --no-cache - --platform linux/amd64 指定构建平台 """ import argparse import subprocess from pathlib import Path # 基础配置 ROOT = Path(__file__).resolve().parents[1] REGISTRY = "docker.pchuan.top" NAMESPACE = "forge" # 目标容器配置 TARGETS = { "ubuntu": { "context": "ubuntu", "image_name": "ubuntu", "build_args": {}, }, "bun": { "context": "bun", "image_name": "bun", "build_args": { "BASE_IMAGE": "docker.pchuan.top/forge/ubuntu:latest", "BUN_REGISTRY": "https://registry.npmmirror.com/", }, }, } parser = argparse.ArgumentParser(description="构建并上传 forge 编译环境镜像") parser.add_argument("target", choices=TARGETS.keys(), help="目标容器") parser.add_argument("--registry", default=REGISTRY, help="镜像仓库地址") parser.add_argument("--namespace", default=NAMESPACE, help="镜像命名空间") parser.add_argument("--tag", default="latest", help="镜像 tag") parser.add_argument("--no-push", action="store_true", help="只构建不上传") parser.add_argument("--force-build", action="store_true", help="强制无缓存构建") parser.add_argument("--platform", default="", help="目标平台,例如 linux/amd64") parser.add_argument("--base-image", default="", help="覆盖 bun 的基础镜像") parser.add_argument("--bun-registry", default="", help="覆盖 bun 的 npm registry") args = parser.parse_args() target = TARGETS[args.target] context = ROOT / target["context"] dockerfile = context / "Dockerfile" image = ( f"{args.registry.rstrip('/')}/" f"{args.namespace.strip('/')}/" f"{target['image_name']}:{args.tag}" ) if not dockerfile.exists(): print(f"[NO] {args.target} Dockerfile exists") print(f"reason: not found: {dockerfile}") raise SystemExit(1) # 真实 .env 和 logs 不参与 SOURCE_VERSION source_files = [ path for path in context.rglob("*") if path.is_file() and path.name != ".env" and "logs" not in path.relative_to(context).parts ] source_version = str(max((path.stat().st_mtime_ns for path in source_files), default=0)) build_args = dict(target["build_args"]) build_args["SOURCE_VERSION"] = source_version if args.target == "bun" and args.base_image: build_args["BASE_IMAGE"] = args.base_image if args.target == "bun" and args.bun_registry: build_args["BUN_REGISTRY"] = args.bun_registry # 直接使用 Dockerfile 构建,不读取目标目录里的 .env build_command = [ "docker", "build", "--file", str(dockerfile), "--tag", image, ] for name, value in build_args.items(): build_command += ["--build-arg", f"{name}={value}"] if args.platform: build_command += ["--platform", args.platform] if args.force_build: build_command += ["--no-cache"] build_command += [str(context)] print(f"[INFO] target={args.target}") print(f"[INFO] image={image}") print("[RUN] Build image") print("[INFO] " + " ".join(build_command)) subprocess.run(build_command, cwd=ROOT, check=True) print("[PASS] Build image") # 默认上传镜像,使用 --no-push 可以只构建不上传 if not args.no_push: push_command = ["docker", "push", image] print("[RUN] Push image") print("[INFO] " + " ".join(push_command)) subprocess.run(push_command, cwd=ROOT, check=True) print("[PASS] Push image") else: print("[INFO] --no-push, skip docker push") print("[PASS] build script completed")