From 5eb50bf3ba5d56e05be401c78692efd792b3774b Mon Sep 17 00:00:00 2001 From: sunjunzhao Date: Sun, 27 Sep 2026 19:29:24 +0800 Subject: [PATCH] revert: drop runtime-image-en language variant --- README.md | 20 +- skills/runtime-image-en/SKILL.md | 193 ------------------ .../references/runtime-matrix.md | 61 ------ .../scripts/detect-registry.sh | 58 ------ 4 files changed, 8 insertions(+), 324 deletions(-) delete mode 100644 skills/runtime-image-en/SKILL.md delete mode 100644 skills/runtime-image-en/references/runtime-matrix.md delete mode 100755 skills/runtime-image-en/scripts/detect-registry.sh diff --git a/README.md b/README.md index a644a42..6b9590d 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ npx skills add gitea@livebook:images/skills.git ``` -按语言选择安装(中文版 `runtime-image` / 英文版 `runtime-image-en`,同一能力的两个语言版本,选一个即可): +只安装指定 skill: ```bash npx skills add gitea@livebook:images/skills.git --skill runtime-image -npx skills add gitea@livebook:images/skills.git --skill runtime-image-en ``` ## 卸载 @@ -33,10 +32,9 @@ npx skills remove runtime-image -g ## Skills 列表 -| Skill | 语言 | 说明 | -| --- | --- | --- | -| [runtime-image](skills/runtime-image/SKILL.md) | 中文 | 探测项目技术栈,按 runtime-base 镜像矩阵选型并生成/修正 Dockerfile | -| [runtime-image-en](skills/runtime-image-en/SKILL.md) | English | English version of runtime-image: detect the tech stack, pick a base image from the runtime-base matrix, generate/audit the Dockerfile | +| Skill | 说明 | +| --- | --- | +| [runtime-image](skills/runtime-image/SKILL.md) | 探测项目技术栈,按 runtime-base 镜像矩阵选型并生成/修正 Dockerfile | ## 目录结构 @@ -44,12 +42,10 @@ npx skills remove runtime-image -g ``` skills/ -├── runtime-image/ # 中文版 -│ ├── SKILL.md # skill 主体(必需) -│ ├── references/ # 补充参考文档 -│ └── scripts/ # 辅助脚本 -└── runtime-image-en/ # English version - └── ... +└── runtime-image/ + ├── SKILL.md # skill 主体(必需) + ├── references/ # 补充参考文档 + └── scripts/ # 辅助脚本 ``` ## License diff --git a/skills/runtime-image-en/SKILL.md b/skills/runtime-image-en/SKILL.md deleted file mode 100644 index 0591de0..0000000 --- a/skills/runtime-image-en/SKILL.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -name: runtime-image-en -description: Detects a project's tech stack and picks the correct base runtime image from the runtime-base image matrix, generating or fixing the project Dockerfile; can also audit an existing Dockerfile and repair it. Use whenever the user asks to "write a Dockerfile / containerize this project / pick a base image / which FROM image / package this into an image / deploy this project as a container / review my Dockerfile / optimize / audit / slim the image", or when symptoms appear such as container won't start / exec format error / image too large / pip compile failures / latest tag behavior changed, or when runtime-node / runtime-pm2 / runtime-base / livebook:8418 / code.innev.cn / registry mirror are mentioned — even if the user never says "runtime image". Scope boundaries — building/pushing app images belongs to project CI or xgent-image-push; modifying the runtime images themselves (adding images, changing versions) belongs to the runtime-base repo, not this skill. English version of the runtime-image skill. ---- - -# runtime-image — Pick a runtime image by project language and environment - -Based on the runtime-base repo's image matrix (10 images: 7 Alpine-based Linux + 3 Wine), complete "detect stack → select image → write Dockerfile → verify on a real machine" inside the project. Two working modes: - -- **Create mode**: the project has no Dockerfile; select from scratch and generate one -- **Audit-and-fix mode**: the project already has a Dockerfile (or the user asks to "look at this Dockerfile"); audit first, then fix - -Core idea (the reasoning chain every constraint hangs on): **the runtime series is only responsible for running already-compiled code** — building/compiling is done by the project's own CI; images contain no compilers/SDKs (the only exception is the Node family: install+build happens at container start, with the node-gyp toolchain included by design). Therefore a project Dockerfile is always a single stage: `FROM runtime-` → `COPY artifacts` → `CMD`. - -## Overall flow - -``` -Pre-step registry detection (script auto-detects internal/external network) -→ Step 0 detect tech stack (also detect existing Dockerfile → switch to audit-and-fix mode if found) -→ Step 1 selection table → Step 2 pin the tag -→ Step 3 write/modify Dockerfile → Step 4 verify on a real machine → Step 5 report -``` - -## Pre-step: registry detection (do this FIRST on every task, never skip) - -There are two registry sources, **decided by detection, never by guesswork**: - -| Source | Prefix | When | Condition | -|---|---|---|---| -| Internal | `livebook:8418/images` | machines on the LAN; fastest pulls | ① daemon.json already lists `insecure-registries: ["livebook:8418"]` (plain HTTP; docker refuses to pull without it) ② `http://livebook:8418/v2/` reachable (200 or 401 both count) | -| External | `code.innev.cn/images` | external machines / distribution to colleagues | fallback when either internal condition fails | - -Run `scripts/detect-registry.sh`; the last stdout line is the image prefix. Every Dockerfile's `FROM` is then written as `/runtime-:`. If both routes fail (exit 1) → die: stop and report troubleshooting directions (internal machine: confirm same subnet + registration + docker restart; external machine: confirm `https://code.innev.cn` is reachable). Do not hardcode either one. - -→ Acceptance: the image prefix used in this task is backed by a detection conclusion (internal/external), recorded in the Step 5 report. - -## Step 0: detect the tech stack (never skip) - -Never pick an image from the user's one-liner — confirm with project files first, then listen to the user's deployment requirements. - -| What you detect | Verdict | -|---|---| -| `package.json` (no build artifacts, not a pure static site) | Node backend → then ask: single process, or multi-process/cluster needed? | -| `package.json` + pure frontend (react/vue/vite static output) | **Not for this series** — CI produces static files hosted by the project's own nginx image | -| `requirements.txt` / `pyproject.toml` | Python | -| `go.mod` / `Cargo.toml` | Go/Rust → static binary → `runtime-base` | -| `pom.xml` / `build.gradle` | Java → jar | -| `*.csproj` / `*.sln` | .NET → **must ask one more question**: modern .NET (publish output) or Framework 4.x? They use completely different images | -| `CMakeLists` / `Makefile` (C/C++) | C/C++ → **must confirm musl dynamic linking** (constraint ⑤) | -| MQL4 / MT4 terminal requirement | `runtime-mt4` | -| Scripts / ops tooling | `runtime-base` | - -**Also detect existing Dockerfiles**: glob all `Dockerfile*` in the project (including subdirectories and `.dockerignore`). If any exist, switch to "audit-and-fix mode" (after Step 2); otherwise continue in create mode. Audit each Dockerfile when there are several. - -→ Acceptance: the verdict has file evidence (paths); anything uncertain is asked about before selection, never guessed; there is an explicit conclusion on whether a Dockerfile already exists. - -## Step 1: selection table - -| Project type | FROM | What the project provides | -|---|---|---| -| Node backend (single process) | `runtime-node` | source code (**without** node_modules); install+build at startup | -| Node backend (multi-process) | `runtime-pm2` | same + pm2 cluster (defaults to max cores) | -| Python | `runtime-python` | source + `venv/` (dependencies installed by CI) | -| Go / Rust static binary | `runtime-base` | compiled binary | -| C/C++ (musl dynamic linking) | `runtime-cpp` | compiled binary | -| Java jar | `runtime-java` | `target/*.jar` | -| Modern .NET publish output | `runtime-dotnet` | `dotnet publish` output directory | -| .NET Framework 4.x | `runtime-wine-dotnet` | `bin/Release/` directory | -| MT4 terminal | `runtime-mt4` | no COPY; mount a data directory at runtime | -| Scripts / tools | `runtime-base` | script files | - -Full image name: `/runtime-:` — the prefix comes from the pre-step detection (internal `livebook:8418/images` / external `code.innev.cn/images`), and can be explicitly overridden by `INNEV_IMAGE_PREFIX` (CI scenarios). For matrix details (preinstalled content / environment variables / Wine notes) read `references/runtime-matrix.md` on demand. - -die: the detected stack is outside the matrix (e.g. Ruby/PHP) → stop and report "no matching runtime image", giving the two options (project builds its own full image / add a new image in the runtime-base repo). **Do not force a near-fit image.** - -→ Acceptance: the chosen image has an explicit row in the table, and every special precondition of that row has been confirmed (musl vs glibc / modern .NET vs 4.x / single vs multi-process). - -## Step 2: pin the tag - -Three tag classes: `latest` (rolling rebuild with upstream every Monday) / `short-sha` / `version tag` (e.g. v0.5.0). - -- Production/delivery projects → pin a `version tag` or `short-sha` (warn: if the user insists on latest, remind them of the rolling-rebuild risk; do not block) -- Local trial runs → latest is acceptable - -→ Acceptance: the FROM tag in the Dockerfile is the result of an explicit choice, not something typed in passing. - -## Audit-and-fix mode (take this branch when Step 0 finds an existing Dockerfile) - -Finish Steps 1 and 2 first to establish "what it should look like" (correct image + tag), then audit the current state item by item. **Read-only audit: change no files until the audit report is out.** - -### Audit checklist (each item carries a hard-constraint number; severity defined below) - -| # | Check | Violates | Severity | -|---|---|---|---| -| A1 | Multi-stage build / SDK installed / compiling inside the image (`RUN mvn package`, `dotnet build`, `pip install` from source) | ① | must-fix | -| A2 | Node family COPYs node_modules / missing `.dockerignore` exclusion / COPYs dist artifacts as a generic pattern (the Node model is install+build at startup) | ② | must-fix | -| A3 | Token passed via `ARG` / secrets COPYed into layers / `.npmrc` with a token landing in image layers | ③ | must-fix (security, handle first) | -| A4 | `FROM ...:latest` in a production/delivery project | ④ | should-fix | -| A5 | C/C++ artifacts built in a glibc environment (debian/ubuntu CI) FROM an Alpine-based image | ⑤ | must-fix | -| A6 | Go/Rust binary FROM a full `golang`/`rust` image or another language-bearing image | ⑥ | should-fix (switch to `runtime-base`; size often drops by an order of magnitude) | -| A7 | Frontend static output served by a node image with `serve`/`http-server` | ⑦ | should-fix (switch to nginx hosting) | -| A8 | Stack/FROM mismatch (Framework 4.x on `runtime-dotnet`, multi-process need running single-process on `runtime-node`, etc.) | table | must-fix | -| A9 | Missing `CMD`/`ENTRYPOINT`, `EXPOSE`; entry path doesn't match the CI artifact path | — | note | -| A10 | FROM prefix contradicts the pre-step detection (external machine FROM `livebook:8418` can't pull; internal machine FROM `code.innev.cn` takes a pointless detour) | ⑧ | must-fix | - -Severity definitions: **must-fix** = will break (won't start / wrong behavior) or leaks secrets; **should-fix** = runs but size/stability/maintainability suffers; **note** = blemish. If only "should-fix/note" items are found and the user asked for an audit only, change no files — the report is the deliverable. - -### Audit report format (fixed) - -- One-line conclusion: currently FROM what → should be FROM what (if identical, say plainly "selection is correct") -- Problem table: severity | line number | problem | basis (constraint number) | fix -- Items with no problems also get a "passed" line (go through A1–A9 one by one; reporting only the bad and never the good is forbidden — omission is as harmful as false positives) - -Lay out the report; once the user confirms fixes → go to Step 3 and rewrite per the template (show the diff), then Step 4 verification. If the user says "audit only, don't touch" → the report is the deliverable. - -## Step 3: write/modify the Dockerfile - -Default action: **lay out the selection conclusion plus the full Dockerfile text for user confirmation before writing to disk** (show a diff when modifying an existing Dockerfile). - -Template (generic pattern; python/java/dotnet/cpp/base/wine are isomorphic): - -```dockerfile -FROM livebook:8418/images/runtime-: -COPY -CMD [...] -``` - -Node family pattern (note the difference from the generic pattern — COPY source, not artifacts): - -```dockerfile -FROM livebook:8418/images/runtime-node: -COPY . . # .dockerignore must exclude node_modules -``` - -Node-family runtime parameters are injected via environment variables at `docker run` time (`APP_ENTRY`/`BUILD_CMD`/`NPM_REGISTRY`/`PM2_APP_NAME`/`PM2_INSTANCES`, etc.; full table in references) — **never baked into image layers**. If an existing Dockerfile uses multi-stage builds / installs SDKs / passes tokens via ARG, call it out per the red lines and rewrite to a single stage. - -→ Acceptance: Dockerfile is single-stage, has no compile steps, contains no secrets; Node family comes with a `.dockerignore` excluding node_modules. - -## Step 4: verify on a real machine - -Done means actually building and smoke-running a container, not "the Dockerfile syntax is fine": - -1. `docker build -t :test .` succeeds -2. `docker run` starts a container and the process stays alive (Node family install+builds on first start — slow startup is normal; watch the logs until listening) -3. If it has a port, verify the port is reachable / the health check passes - -If the environment doesn't allow build verification (host hasn't registered `livebook:8418` as an insecure registry, no docker) → explicitly declare "not verified" and list "left for manual follow-up" in the report. - -## Step 5: report - -Fixed structure: -- Mode: create / audit-and-fix -- Registry: internal / external (detection conclusion, with script output as evidence) -- Detection conclusion: language/stack + evidence file paths -- Selection: `runtime-:` + one-line rationale -- [Audit-and-fix mode] audit results: must-fix N / should-fix N / notes N (with the problem table); whether the user authorized fixes -- What was written: Dockerfile (new/modified diff), `.dockerignore` -- What was skipped/not done: e.g. multi-process need unconfirmed, CI build step missing, user asked for audit only -- How far verification went: build/run results — **if not done, say not done** -- Left for manual follow-up: daemon.json registration, CI artifact steps, secret configuration - -## Hard constraints - -- ① **No compilation in runtime images**. Why: images carry no compilers/SDKs (dotnet has no SDK, java has no JDK/Maven, python has no musl compile chain); installing one defeats the "minimal runtime base" purpose. Symptom: build-time `mvn/dotnet SDK not found`, pip source-compile failures. Sole exception: the Node family install+builds at startup (node-gyp toolchain included — by design, not a loophole). -- ② **Node family never COPYs node_modules**. Why: the entrypoint runs `pnpm install` at startup (`--frozen-lockfile` with a lockfile); COPYing it in causes overwrite conflicts and image bloat; native modules must compile inside the container to match the runtime environment. -- ③ **Tokens never via ARG, never in image layers**. Why: ARG values persist in `docker history`; `NPM_TOKEN` via env shows up in `docker inspect` output. Correct posture: `NPM_TOKEN_FILE` pointing at a secret file (docker secret / k8s mounted volume); the token is written only into the in-container `~/.npmrc`. -- ④ **No latest in production**. Why: `latest` rolls with upstream every Monday 02:30 — the content under the same tag changes; "it ran last time" doesn't mean it runs next time. Pin a version tag or short sha. -- ⑤ **C/C++ only with musl dynamic linking on `runtime-cpp`**. Why: Alpine is musl-based; glibc artifacts won't run — symptom `exec format error` or `not found` (binary exists but the loader is missing). For glibc artifacts, switch base or compile statically. -- ⑥ **No dedicated Go/Rust image**. A static native binary needs no language environment at runtime — `FROM runtime-base` is enough; don't go looking for `runtime-go`. -- ⑦ **Frontend doesn't use this series**. React/Vue etc. are built by CI into static files and hosted by the project's own nginx image. -- ⑧ **Registry source is decided by pre-step detection — never guessed, never hardcoded**. Why: internal `livebook:8418` is LAN plain HTTP, unreachable from outside; external `code.innev.cn` is a detour from inside. Hardcode either route and pulling breaks when the network environment changes. Run `scripts/detect-registry.sh` first on every task; for scenarios needing a fixed source (CI), set `INNEV_IMAGE_PREFIX` explicitly and say so in the report. - -## Troubleshooting table - -| Symptom | Cause | What to do | -|---|---|---| -| `exec format error` | glibc binary in a musl image / architecture mismatch | switch the build stage to musl or static compilation; check buildx architecture | -| Pull fails with `http: server gave HTTP response to HTTPS client` | host hasn't registered `insecure-registries: ["livebook:8418"]` | edit `/etc/docker/daemon.json` and restart docker (manual); or switch to the external prefix per pre-step detection | -| External machine FROM `livebook:8418` times out / connection refused | internal prefix is only reachable on the LAN | change FROM to `code.innev.cn/images/...` (re-run pre-step detection to confirm) | -| External pull returns 401 unauthorized | code.innev.cn registry requires auth | `docker login code.innev.cn` (manual, with the Gitea account) | -| pip install compile failure | runtime-python has no compile chain | compile wheels/venv in the build stage (CI), then COPY | -| .NET program won't start on runtime-dotnet | Framework 4.x program on the modern .NET image | switch to `runtime-wine-dotnet`, start with `xvfb-run -a wine` | -| Token visible in `docker inspect` | NPM_TOKEN passed via env | switch to NPM_TOKEN_FILE secret mount | -| Worked last week, broken this week | used latest; Monday rolling rebuild | pin a version tag / short sha | -| Node container starts very slowly | normal: install+build at startup | watch logs until listening; don't kill it as hung | - -## Red lines (never violated under any circumstance) - -- Never select by guesswork — if Step 0 finds no evidence, ask; never force a near-fit image -- Never add compile stages/SDKs to a project Dockerfile (except the Node family's startup build) -- Never write any token/secret into a Dockerfile or image layer; a token the user pasted must not land in any file — and remind them to rotate it -- Never silently drop anything — skipped requirements (multi-process/private registry/health check) must appear in the report -- Before changing an existing Dockerfile, present the conclusion and the diff first; write only after confirmation diff --git a/skills/runtime-image-en/references/runtime-matrix.md b/skills/runtime-image-en/references/runtime-matrix.md deleted file mode 100644 index dc948ee..0000000 --- a/skills/runtime-image-en/references/runtime-matrix.md +++ /dev/null @@ -1,61 +0,0 @@ -# runtime-base image matrix details (excerpted from the runtime-base repo README, 2026-09 edition) - -> Read on demand: consult this file when Step 1 leaves you unsure about preinstalled content or environment variables. -> Full image name: `$INNEV_IMAGE_PREFIX/runtime-:`, default prefix `livebook:8418/images/`. - -## Linux side (all Alpine, linux/amd64 + linux/arm64 dual-arch) - -| Image | Base | Preinstalled | -|---|---|---| -| `runtime-base` | alpine:3.22 | bash/git/curl/jq/make/CA certs/UTC+8 timezone; the runtime foundation for Go/Rust static binaries and scripts | -| `runtime-node` | node:24-alpine | Node backend: pnpm install + build at startup, launched with `node $APP_ENTRY` | -| `runtime-pm2` | node:24-alpine | same + pm2: `pm2-runtime` cluster startup | -| `runtime-python` | python:3.13-alpine | pip/setuptools/wheel + uv/pipx/virtualenv (no source compile chain — packages without musl wheels are compiled in the build stage) | -| `runtime-dotnet` | dotnet/aspnet:10.0-alpine | ASP.NET Core runtime, no SDK | -| `runtime-cpp` | alpine:3.22 | libstdc++/libgcc runtime libraries (musl dynamically linked artifacts only) | -| `runtime-java` | temurin:21-jre-alpine | JRE 21 LTS, no JDK/Maven/Gradle | - -The Node family (node/pm2) additionally preinstalls: pnpm/yarn (versions controllable via ARG `PNPM_VERSION`/`YARN_VERSION`), python3/make/g++ (the node-gyp compile chain) — because they run `pnpm install` at container start, and native modules compile at install time right there. - -## Windows side (Linux containers + Wine, x86_64 only) - -| Image | Base | Preinstalled | -|---|---|---| -| `runtime-wine-base` | debian:bookworm-slim | Wine (win32+win64) + winetricks + Xvfb + CJK fonts + pre-initialized wine prefix `/opt/wineprefix` | -| `runtime-wine-dotnet` | runtime-wine-base | .NET Framework 4.8 preinstalled (ARG `DOTNET_FX` switches the verb) | -| `runtime-mt4` | runtime-wine-base | MetaTrader 4 + corefonts preinstalled, Xvfb headless startup | - -Why Wine instead of Windows containers: Windows containers require a Windows Server host — high maintenance cost; .NET Framework 4.x and MT4 are mature and stable under Wine. Debian as base rather than Alpine: musl Wine has incomplete 32-bit (wow64) support. - -## Node-family environment variables (inject at docker run, never bake into layers) - -| Variable | Purpose | Default | -|---|---|---| -| `NPM_REGISTRY` | npm registry address (including private ones) | official registry | -| `NPM_TOKEN` | private registry token (via env it ends up in `docker inspect`; not recommended) | none | -| `NPM_TOKEN_FILE` | token file path (docker secret / k8s mounted volume; recommended, takes precedence over NPM_TOKEN) | none | -| `APP_DIR` | app directory | `/app` | -| `BUILD_CMD` | build script name (auto-skipped if absent from package.json) | `build` | -| `APP_ENTRY` | build artifact entry file | `dist/main.js` | -| `PM2_APP_NAME` | [pm2] app name | `app` | -| `PM2_INSTANCES` | [pm2] cluster instance count | `max` (core count) | - -## Common CMD / COPY patterns - -- 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: no COPY; `docker run -v /srv/mt4/data:/opt/wineprefix/drive_c/"Program Files (x86)"/"MetaTrader 4"`; drop EAs/indicators into the data directory's `MQL4/Experts`, `MQL4/Indicators` and restart the container - -## Pre-step: host registration on the pulling side (manual) - -Add `{ "insecure-registries": ["livebook:8418"] }` to `/etc/docker/daemon.json` and restart docker (the internal registry uses plain HTTP on the LAN). - -## Tag policy - -`latest` (rolling rebuild with upstream every Monday 02:30 Beijing time) / `short-sha` / `version tag` (v*). Production pins a version tag or short sha. - -## Package-manager mirrors for China - -Each runtime Dockerfile has comments showing how to enable them; you can also configure them in the project's build-stage Dockerfile (the README provides ready-made snippets for crates.io rsproxy and Maven aliyun). diff --git a/skills/runtime-image-en/scripts/detect-registry.sh b/skills/runtime-image-en/scripts/detect-registry.sh deleted file mode 100755 index e94ad3a..0000000 --- a/skills/runtime-image-en/scripts/detect-registry.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -# detect-registry.sh — detect the reachable registry and print the image prefix -# Internal livebook:8418 preferred (plain HTTP on the LAN, fast); external code.innev.cn as fallback. -# Usage: scripts/detect-registry.sh -# Output: last stdout line is the image prefix (e.g. livebook:8418/images); exit 1 = both routes failed -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 "▸ $*"; } - -# --- Check 1: is livebook:8418 registered in insecure-registries (internal is plain HTTP; docker refuses to pull otherwise) --- -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 has $IN_HOST registered" || no "insecure-registries missing $IN_HOST" - -# --- Check 2: internal registry connectivity (/v2/ returning 200 or 401 both count) --- -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 "internal $IN_HOST reachable (HTTP ${in_code})" -else - no "internal $IN_HOST unreachable (HTTP ${in_code})" -fi - -if [ "$insecure_ok" -eq 1 ] && { [ "$in_code" = 200 ] || [ "$in_code" = 401 ]; }; then - step "conclusion: use internal registry (fastest build pulls)" - printf '%s\n' "$IN_PREFIX" - exit 0 -fi - -# --- Fallback: external --- -step "internal unavailable, trying external $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 "external $OUT_HOST reachable (HTTP ${out_code})" - say "⚠ using external: slower pulls than internal; if the registry requires auth, run docker login $OUT_HOST first (manual)" - printf '%s\n' "$OUT_PREFIX" - exit 0 -fi - -no "external $OUT_HOST also unreachable (HTTP ${out_code})" -say "Both routes failed. Troubleshoot: internal machine — confirm same subnet as $IN_HOST, daemon.json registered, docker restarted; external machine — confirm https://$OUT_HOST is reachable" -exit 1