feat: add runtime-image-select skill

Base runtime image selection and Dockerfile generation/audit per the
runtime-base image matrix, packaged as a skills.sh-compliant skill.
This commit is contained in:
sunjunzhao committed 2026-09-27 19:02:11 +08:00
commit 201f6c1054
5 files changed
+372

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 XGENT.ai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+39
View File
@@ -0,0 +1,39 @@
# Images Skills
[Agent Skills](https://skills.sh) 集合,可安装到 Claude Code、Cursor、Codex 等支持 skill 的 Agent 中。
## 安装
安装全部 skills:
```bash
npx skills add gitea@livebook:images/skills.git
```
只安装指定 skill:
```bash
npx skills add gitea@livebook:images/skills.git --skill runtime-image-select
```
## Skills 列表
| Skill | 说明 |
| --- | --- |
| [runtime-image-select](skills/runtime-image-select/SKILL.md) | 探测项目技术栈,按 runtime-base 镜像矩阵选型并生成/修正 Dockerfile |
## 目录结构
每个 skill 位于 `skills/<name>/` 目录下,包含一个带 `name` 与 `description` frontmatter 的 `SKILL.md`:
```
skills/
└── runtime-image-select/
├── SKILL.md # skill 主体(必需)
├── references/ # 补充参考文档
└── scripts/ # 辅助脚本
```
## License
[MIT](LICENSE)
+193
View File
@@ -0,0 +1,193 @@
---
name: runtime-image-select
description: 在具体项目中探测技术栈并按 runtime-base 镜像矩阵选择正确的基础运行时镜像、生成/修正项目 Dockerfile;也能对已存在的 Dockerfile 做评估审计并修正。凡用户要求「写 Dockerfile / 容器化这个项目 / 选基础镜像 / FROM 哪个镜像 / 帮我打包成镜像 / 这个项目部署成容器 / 评估一下这个 Dockerfile / 帮我看看 Dockerfile 有没有问题 / 优化 / 评审 / 精简镜像」,或出现 容器起不来 / exec format error / 镜像太大 / pip 安装编译失败 / latest 镜像行为变了 等症状,或提到 runtime-node / runtime-pm2 / runtime-base / livebook:8418 / code.innev.cn / 镜像源 时,务必使用本 skill——即使用户没说「运行时镜像」四个字。Use whenever the user needs to pick a base runtime image (runtime-<name> matrix), write a project Dockerfile, or audit/fix an existing Dockerfile. 边界:构建/推送业务镜像归项目 CI 或 xgent-image-push;修改 runtime 镜像本身(加镜像/改版本)归 runtime-base 仓库维护,不用本 skill。
---
# runtime-image-select — 按项目语言与环境选运行时镜像
依据 runtime-base 仓库的镜像矩阵(10 个镜像:Linux 7 个 Alpine 系 + Wine 3 个),在项目里完成「探测栈 → 选型 → 写 Dockerfile → 真机验证」。两种工作模式:
- **新建模式**:项目没有 Dockerfile,从零选型生成
- **评估修正模式**:项目已有 Dockerfile(或用户要求"看看这个 Dockerfile"),先审计再修正
核心理念(推理链,所有约束都挂在这上面):**runtime 系列只负责把编译好的代码跑起来**——构建/编译由项目自己的 CI 完成,镜像内无编译器/SDK(Node 系唯一例外:容器启动时才 install+build,自带 node-gyp 链)。因此项目 Dockerfile 恒为单段:`FROM runtime-<name>` → `COPY 产物` → `CMD`。
## 总流程
```
前置 镜像源探测(脚本自动判内网/外网)
→ 第 0 步 探测技术栈(同时探测已有 Dockerfile → 有则转评估修正模式)
→ 第 1 步 选型对照 → 第 2 步 定 tag
→ 第 3 步 写/改 Dockerfile → 第 4 步 真机验证 → 第 5 步 汇报
```
## 前置:镜像源探测(每次任务最先做,绝不可跳过)
镜像源有两路,**由探测决定,不许凭经验猜**:
| 源 | 前缀 | 适用 | 条件 |
|---|---|---|---|
| 内网 | `livebook:8418/images` | 内网机器,构建拉取最快 | ① daemon.json 已登记 `insecure-registries: ["livebook:8418"]`(明文 HTTP,不登记 docker 拒拉)② `http://livebook:8418/v2/` 可达(200/401 均算通) |
| 外网 | `code.innev.cn/images` | 外网/分发给同事使用 | 内网任一条件不满足时兜底 |
执行 `scripts/detect-registry.sh`,stdout 最后一行即镜像前缀;之后所有 Dockerfile 的 `FROM` 统一写成 `<前缀>/runtime-<name>:<tag>`。两路都不通(exit 1)→ die:停下报排查方向(内网机器确认同网段+登记+重启 docker;外网机器确认能访问 `https://code.innev.cn`),不硬写任何一个。
→ 验收:本次任务的镜像前缀有探测结论(内网/外网)背书,并记入第 5 步汇报。
## 第 0 步:探测技术栈(绝不可跳过)
不许凭用户一句话定镜像——读项目文件坐实,再听用户补充部署需求。
| 探测到什么 | 判定 |
|---|---|
| `package.json`(无构建产物直出静态页) | Node 后端 → 再问:单进程还是多进程/需要 cluster? |
| `package.json` + 纯前端(react/vue/vite 静态产物) | **不走本系列**,CI 出静态文件由项目自己的 nginx 镜像托管 |
| `requirements.txt` / `pyproject.toml` | Python |
| `go.mod` / `Cargo.toml` | Go/Rust → 静态二进制 → `runtime-base` |
| `pom.xml` / `build.gradle` | Java → jar |
| `*.csproj` / `*.sln` | .NET → **必须再问一句**:现代 .NET(publish 产物)还是 Framework 4.x?两者走完全不同的镜像 |
| `CMakeLists` / `Makefile`(C/C++) | C/C++ → **必须确认 musl 动态链接**(见约束⑤) |
| MQL4 / MT4 终端需求 | `runtime-mt4` |
| 脚本/运维工具 | `runtime-base` |
**同时探测已有 Dockerfile**:glob 项目内所有 `Dockerfile*`(含子目录、`.dockerignore`)。存在即转入「评估修正模式」(见第 2 步之后);不存在走新建模式顺次执行。多个 Dockerfile 逐一评估。
→ 验收:判定结论有文件证据(路径),拿不准的在选型前问清,不猜;是否已有 Dockerfile 有明确结论。
## 第 1 步:选型对照
| 项目类型 | FROM | 项目提供什么 |
|---|---|---|
| Node 后端(单进程) | `runtime-node` | 源码(**不含** node_modules);启动时 install+build |
| Node 后端(多进程) | `runtime-pm2` | 同上 + pm2 cluster(缺省 max 核数) |
| Python | `runtime-python` | 源码 + `venv/`(CI 装好依赖) |
| Go / Rust 静态二进制 | `runtime-base` | 编译好的二进制 |
| C/C++(musl 动态链接) | `runtime-cpp` | 编译好的二进制 |
| Java jar | `runtime-java` | `target/*.jar` |
| 现代 .NET publish 产物 | `runtime-dotnet` | `dotnet publish` 输出目录 |
| .NET Framework 4.x | `runtime-wine-dotnet` | `bin/Release/` 目录 |
| MT4 终端 | `runtime-mt4` | 不 COPY,运行时挂数据目录 |
| 脚本/工具 | `runtime-base` | 脚本文件 |
镜像全名:`<前缀>/runtime-<name>:<tag>`——前缀由前置探测给出(内网 `livebook:8418/images` / 外网 `code.innev.cn/images`),也可被 `INNEV_IMAGE_PREFIX` 显式覆盖(CI 场景)。矩阵详情(预装内容/环境变量/Wine 说明)读 `references/runtime-matrix.md`,按需读取。
die:探测结果不在矩阵内(如 Ruby/PHP)→ 停下报"无匹配运行时镜像",给两个去向(项目自建完整镜像 / 去 runtime-base 仓库加新镜像),**不硬塞一个近似的**。
→ 验收:选出的镜像在对照表中有明确一行,且该行的特殊前提已逐条确认(musl 还是 glibc / 现代 .NET 还是 4.x / 单进程还是多进程)。
## 第 2 步:定 tag
三类 tag:`latest`(每周一随上游滚动重建)/ `短sha` / `版本标签`(如 v0.5.0)。
- 生产/交付项目 → 锁 `版本标签` 或 `短sha`(warn:用户坚持用 latest 时提醒滚动重建风险,不拦)
- 本地试跑 → latest 可接受
→ 验收:Dockerfile 里 FROM 的 tag 是显式选择的结果,不是顺手写的。
## 评估修正模式(第 0 步发现已有 Dockerfile 时走此分支)
先做完第 1、2 步得出"应该长什么样"(正确镜像 + tag),再拿现状逐项对照审计。**只读审计,审计报告出来之前不改任何文件**。
### 审计清单(每条挂着硬约束编号,级别定义见下)
| # | 检查项 | 违反 | 级别 |
|---|---|---|---|
| A1 | 多段构建 / 装了 SDK / 镜像内编译(`RUN mvn package`、`dotnet build`、`pip install` 源码编译) | ① | 必须修 |
| A2 | Node 系 COPY 了 node_modules / 缺 `.dockerignore` 排除 / 把 dist 产物当通用模式 COPY(Node 系模型是启动时 install+build) | ② | 必须修 |
| A3 | `ARG` 传令牌 / 密钥 COPY 进层 / `.npmrc` 含 token 落镜像层 | ③ | 必须修(安全项,优先处理) |
| A4 | `FROM ...:latest` 且是生产/交付项目 | ④ | 建议修 |
| A5 | glibc 环境(debian/ubuntu CI)构建的 C/C++ 产物 FROM alpine 系 | ⑤ | 必须修 |
| A6 | Go/Rust 二进制 FROM `golang`/`rust` 全镜像或其他带语言环境的镜像 | ⑥ | 建议修(换 `runtime-base`,体积常减一个数量级) |
| A7 | 前端静态产物用 node 镜像 `serve`/`http-server` 跑 | ⑦ | 建议修(换 nginx 托管) |
| A8 | 栈与 FROM 错配(Framework 4.x 用 `runtime-dotnet`、多进程需求用 `runtime-node` 单进程跑等) | 选型表 | 必须修 |
| A9 | 缺 `CMD`/`ENTRYPOINT`、`EXPOSE`、入口路径与 CI 产物路径对不上 | — | 提示 |
| A10 | FROM 前缀与前置探测结论不符(外网机器 FROM `livebook:8418` 拉不动;内网机器 FROM `code.innev.cn` 白白绕远) | ⑧ | 必须修 |
级别定义:**必须修** = 会挂(起不来/行为错)或泄密;**建议修** = 能跑但体积/稳定性/可维护性受损;**提示** = 瑕疵。只发现"建议修/提示"且用户只要评估时,不改文件,出报告即止。
### 审计报告格式(固定)
- 结论一行:当前 FROM 什么 → 应该 FROM 什么(一致则明说"选型正确")
- 问题表:级别 | 行号 | 问题 | 依据(约束编号) | 修法
- 没问题的项也要说一句"已过"(A1-A9 逐条过,不许只报坏不报好——漏报和误报一样有害)
报告摊开、用户确认要修 → 转第 3 步按模板改写(给 diff),再进第 4 步验证。用户说"只评估不动手"→ 报告即交付物。
## 第 3 步:写/改 Dockerfile
默认动作:**先把选型结论 + 拟写的 Dockerfile 全文摊给用户确认,再落盘**(改已有 Dockerfile 时给 diff)。
模板(通用模式,python/java/dotnet/cpp/base/wine 同构):
```dockerfile
FROM livebook:8418/images/runtime-<name>:<tag>
COPY <CI 构建好的产物> <目标路径>
CMD [...]
```
Node 系模式(注意与通用模式不同——COPY 的是源码不是产物):
```dockerfile
FROM livebook:8418/images/runtime-node:<tag>
COPY . . # .dockerignore 必须排除 node_modules
```
Node 系运行参数在 `docker run` 时经环境变量注入(`APP_ENTRY`/`BUILD_CMD`/`NPM_REGISTRY`/`PM2_APP_NAME`/`PM2_INSTANCES` 等,全表见 references),**不落镜像层**。已有 Dockerfile 若用了多段构建/装了 SDK/ARG 传令牌,按红线逐条指出并改写为单段。
→ 验收:Dockerfile 单段、无编译步骤、无密钥;Node 系配套 `.dockerignore` 排除 node_modules。
## 第 4 步:真机验证
完成定义 = 真机构建并起容器冒烟,不是"Dockerfile 语法没问题":
1. `docker build -t <app>:test .` 成功
2. `docker run` 起容器,进程活着(Node 系首启会 install+build,启动慢是正常的,看日志等到 listening)
3. 有端口的验证端口可达 / 健康检查通过
环境不允许构建验证(宿主机没登记 `livebook:8418` 为 insecure-registry、无 docker)→ 显式声明"未验证",并把"待人工补"列进汇报。
## 第 5 步:汇报
固定结构:
- 模式:新建 / 评估修正
- 镜像源:内网 / 外网(探测结论,脚本输出佐证)
- 探测结论:语言/栈 + 证据文件路径
- 选型:`runtime-<name>:<tag>` + 一句话理由
- 【评估修正模式】审计结果:必须修 N 条 / 建议修 N 条 / 提示 N 条(附问题表),用户是否授权修正
- 写了什么:Dockerfile(新建/修改 diff)、`.dockerignore`
- 被跳过/未做的:如多进程需求未确认、CI 构建步骤缺失、用户只让评估没让修
- 验证做到哪一步:build/run 结果,**没做就说没做**
- 待人工补:daemon.json 登记、CI 构建产物步骤、secret 配置
## 硬约束
- ① **编译不进 runtime 镜像**。为什么:镜像内无编译器/SDK(dotnet 无 SDK、java 无 JDK/Maven、python 无 musl 编译链),装了即失去"最小运行基座"意义;症状是构建期 `mvn/dotnet SDK not found`、pip 源码编译失败。唯一例外:Node 系启动时 install+build(自带 node-gyp 链,这是设计如此,不是漏洞)。
- ② **Node 系不 COPY node_modules**。为什么:entrypoint 启动时 `pnpm install`(有 lockfile 走 `--frozen-lockfile`),COPY 进去会覆盖冲突且镜像膨胀;原生模块在容器内编译才匹配运行环境。
- ③ **令牌不用 ARG 传、不落镜像层**。为什么:ARG 值留在 `docker history`;`NPM_TOKEN` 走 env 会出现在 `docker inspect` 输出。正确姿势:`NPM_TOKEN_FILE` 指 secret 文件(docker secret / k8s 挂载卷),令牌只写进容器内 `~/.npmrc`。
- ④ **生产不用 latest**。为什么:`latest` 每周一 02:30 随上游滚动重建,同一 tag 下面内容会变——上次能跑不代表下次能跑。锁版本标签或短 sha。
- ⑤ **C/C++ 仅 musl 动态链接适用 `runtime-cpp`**。为什么:Alpine 是 musl 系统,glibc 产物跑不起来,症状 `exec format error` 或 `not found`(二进制存在但加载器缺失)。glibc 产物请换基座或静态编译。
- ⑥ **Go/Rust 没有独立镜像**。静态原生二进制运行时不需要语言环境,`FROM runtime-base` 即可——不要去找 `runtime-go`。
- ⑦ **前端不走本系列**。React/Vue 等由 CI 构建静态文件,项目自己的 nginx 镜像托管。
- ⑧ **镜像源由前置探测决定,不猜不写死**。为什么:内网 `livebook:8418` 是 LAN 明文 HTTP,外网根本够不着;外网 `code.innev.cn` 在内网绕远。FROM 写死任何一路,换个网络环境就拉不动。每次任务先跑 `scripts/detect-registry.sh`;CI 等需要固定的场景用 `INNEV_IMAGE_PREFIX` 显式指定,并在汇报里说明。
## 排障对照表
| 症状 | 原因 | 怎么办 |
|---|---|---|
| `exec format error` | glibc 二进制进 musl 镜像 / 架构不符 | 构建阶段改 musl 或静态编译;确认 buildx 架构 |
| 拉镜像 `http: server gave HTTP response to HTTPS client` | 宿主未登记 `insecure-registries: ["livebook:8418"]` | 改 `/etc/docker/daemon.json` 并重启 docker(人工);或按前置探测改走外网前缀 |
| 外网机器 FROM `livebook:8418` 超时/拒连 | 内网前缀只在内网可达 | FROM 换 `code.innev.cn/images/...`(重跑前置探测确认) |
| 外网拉取 401 unauthorized | code.innev.cn 仓库要求认证 | `docker login code.innev.cn`(人工,用 Gitea 账号) |
| pip 安装编译失败 | runtime-python 无编译链 | 构建阶段(CI)编译好 wheel/venv 再 COPY |
| .NET 程序在 runtime-dotnet 起不来 | Framework 4.x 程序走了现代 .NET 镜像 | 换 `runtime-wine-dotnet`,`xvfb-run -a wine` 启动 |
| 令牌出现在 `docker inspect` | NPM_TOKEN 走了 env | 改 NPM_TOKEN_FILE secret 挂载 |
| 上周能跑这周不能 | 用了 latest,周一滚动重建 | 锁版本标签/短 sha |
| Node 容器启动很慢 | 正常:启动时 install+build | 看日志等到 listening;别当挂起杀掉 |
## 红线(任何情况下不违反)
- 不凭猜测选型——第 0 步探测不到证据就问,不硬塞近似镜像
- 不给项目 Dockerfile 加编译段/SDK(Node 系启动时构建除外)
- 不在 Dockerfile/镜像层里写任何令牌密钥;用户贴过来的令牌不落进任何文件,并提醒轮换
- 不静默截断——跳过的需求(多进程/私有源/健康检查)必须出现在汇报里
- 改已有 Dockerfile 前先给结论和 diff,确认后再写
@@ -0,0 +1,61 @@
# runtime-base 镜像矩阵详情(摘自 runtime-base 仓库 README,2026-09 版)
> 按需读取:SKILL.md 第 1 步拿不准预装内容/环境变量时再查本文件。
> 镜像全名:`$INNEV_IMAGE_PREFIX/runtime-<name>:<tag>`,默认前缀 `livebook:8418/images/`。
## Linux 侧(全部 Alpine,linux/amd64 + linux/arm64 双架构)
| 镜像 | 基座 | 预装内容 |
|---|---|---|
| `runtime-base` | alpine:3.22 | bash/git/curl/jq/make/CA 证书/东八区时区;Go/Rust 静态二进制、脚本的运行地基 |
| `runtime-node` | node:24-alpine | Node 后端:启动时 pnpm install + build,`node $APP_ENTRY` 启动 |
| `runtime-pm2` | node:24-alpine | 同上 + pm2:`pm2-runtime` cluster 启动 |
| `runtime-python` | python:3.13-alpine | pip/setuptools/wheel + uv/pipx/virtualenv(无源码编译链——无 musl wheel 的包在构建阶段编译) |
| `runtime-dotnet` | dotnet/aspnet:10.0-alpine | ASP.NET Core 运行时,无 SDK |
| `runtime-cpp` | alpine:3.22 | libstdc++/libgcc 运行库(仅 musl 动态链接产物) |
| `runtime-java` | temurin:21-jre-alpine | JRE 21 LTS,无 JDK/Maven/Gradle |
Node 系(node/pm2)额外预装:pnpm/yarn(ARG `PNPM_VERSION`/`YARN_VERSION` 可控版本)、python3/make/g++(node-gyp 编译链)——因为它们在容器启动时才 `pnpm install`,原生模块安装期编译直接可用。
## Windows 侧(Linux 容器 + Wine,仅 x86_64)
| 镜像 | 基座 | 预装内容 |
|---|---|---|
| `runtime-wine-base` | debian:bookworm-slim | Wine(win32+win64) + winetricks + Xvfb + 中文字体 + 预初始化 wine 前缀 `/opt/wineprefix` |
| `runtime-wine-dotnet` | runtime-wine-base | 预装 .NET Framework 4.8(ARG `DOTNET_FX` 可切 verb) |
| `runtime-mt4` | runtime-wine-base | 预装 MetaTrader 4 + corefonts,Xvfb headless 启动 |
为什么是 Wine 不是 Windows 容器:Windows 容器需要 Windows Server 宿主机,维护成本高;.NET Framework 4.x 与 MT4 在 Wine 下成熟稳定。基座选 Debian 不选 Alpine:musl 版 Wine 对 32 位(wow64)支持不完整。
## Node 系环境变量(docker run 时注入,不落镜像层)
| 变量 | 作用 | 默认值 |
|---|---|---|
| `NPM_REGISTRY` | npm 仓库地址(含私有源) | 官方源 |
| `NPM_TOKEN` | 私有仓库令牌(走 env 会进 `docker inspect`,不推荐) | 无 |
| `NPM_TOKEN_FILE` | 令牌文件路径(docker secret / k8s 挂载卷,推荐,优先于 NPM_TOKEN) | 无 |
| `APP_DIR` | 应用目录 | `/app` |
| `BUILD_CMD` | 构建脚本名(package.json 无此脚本自动跳过) | `build` |
| `APP_ENTRY` | 构建产物入口文件 | `dist/main.js` |
| `PM2_APP_NAME` | 【pm2】应用名 | `app` |
| `PM2_INSTANCES` | 【pm2】cluster 实例数 | `max`(核数) |
## 常用 CMD / COPY 范式
- Go:`COPY app /usr/local/bin/app` + `CMD ["app"]`
- Java:`COPY target/app.jar /app/app.jar` + `CMD ["java","-jar","/app/app.jar"]`
- .NET:`COPY out /app` + `CMD ["dotnet","/app/MyService.dll"]`
- wine-dotnet:`COPY bin/Release/ "$WINEPREFIX/drive_c/app/"` + `CMD ["xvfb-run","-a","wine","C:\\app\\MyService.exe"]`
- MT4:不 COPY;`docker run -v /srv/mt4/data:/opt/wineprefix/drive_c/"Program Files (x86)"/"MetaTrader 4"`,EA/指标放数据目录 `MQL4/Experts`、`MQL4/Indicators` 后重启容器
## 前置:拉取侧宿主机登记(人工)
`/etc/docker/daemon.json` 加 `{ "insecure-registries": ["livebook:8418"] }` 并重启 docker(内网 registry 走 LAN 明文 HTTP)。
## Tag 策略
`latest`(每周一 02:30 北京时间随上游滚动重建)/ `短sha` / `版本标签`(v*)。生产锁版本标签或短 sha。
## 包管理国内镜像源
各 runtime Dockerfile 注释里有开启方式,也可在项目构建阶段 Dockerfile 自行配置(README 给了 crates.io rsproxy 与 Maven aliyun 的现成片段)。
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# detect-registry.sh — 探测可用镜像源,输出镜像前缀
# 内网 livebook:8418 优先(LAN 明文 HTTP,快);外网 code.innev.cn 兜底。
# 用法:scripts/detect-registry.sh
# 输出:stdout 最后一行为镜像前缀(如 livebook:8418/images);exit 1 = 两路都不通
set -u
IN_HOST="livebook:8418"
IN_PREFIX="livebook:8418/images"
OUT_HOST="code.innev.cn"
OUT_PREFIX="code.innev.cn/images"
say() { printf '%s\n' "$*" >&2; }
ok() { say "✔ $*"; }
no() { say "✘ $*"; }
step() { say "▸ $*"; }
# --- 检查 1:insecure-registries 是否登记 livebook:8418(内网是明文 HTTP,不登记 docker 拒拉)---
insecure_ok=0
if command -v docker >/dev/null 2>&1; then
if docker info 2>/dev/null | grep -qi "$IN_HOST"; then
insecure_ok=1
fi
fi
if [ "$insecure_ok" -eq 0 ]; then
for f in /etc/docker/daemon.json "$HOME/.docker/daemon.json"; do
[ -f "$f" ] && grep -q "$IN_HOST" "$f" && insecure_ok=1 && break
done
fi
[ "$insecure_ok" -eq 1 ] && ok "insecure-registries 已登记 $IN_HOST" || no "insecure-registries 未登记 $IN_HOST"
# --- 检查 2:内网 registry 连通性(/v2/ 返回 200 或 401 都算通)---
in_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 4 "http://$IN_HOST/v2/" 2>/dev/null || echo 000)
if [ "$in_code" = 200 ] || [ "$in_code" = 401 ]; then
ok "内网 $IN_HOST 可达(HTTP ${in_code})"
else
no "内网 $IN_HOST 不可达(HTTP ${in_code})"
fi
if [ "$insecure_ok" -eq 1 ] && { [ "$in_code" = 200 ] || [ "$in_code" = 401 ]; }; then
step "结论:走内网(构建拉取最快)"
printf '%s\n' "$IN_PREFIX"
exit 0
fi
# --- 兜底:外网 ---
step "内网不可用,尝试外网 $OUT_HOST"
out_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 6 "https://$OUT_HOST/v2/" 2>/dev/null || echo 000)
if [ "$out_code" = 200 ] || [ "$out_code" = 401 ]; then
ok "外网 $OUT_HOST 可达(HTTP ${out_code})"
say "⚠ 走外网:拉取速度不如内网;若外网仓库要求认证,先 docker login $OUT_HOST(人工)"
printf '%s\n' "$OUT_PREFIX"
exit 0
fi
no "外网 $OUT_HOST 也不可达(HTTP ${out_code})"
say "两路都不通。排查:内网机器确认与 $IN_HOST 同网段且 daemon.json 已登记并重启 docker;外网机器确认能访问 https://$OUT_HOST"
exit 1