From 8edbec02cfb06e19ed0e1cd2117c2ac5420ce4b2 Mon Sep 17 00:00:00 2001 From: khanhphamj Date: Sat, 31 Oct 2026 10:55:54 +0700 Subject: [PATCH] Deploy skill: detect how the customer can build and push images (Docker, Rancher, Podman, GitHub, GitLab) Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 6 + skills/agentbase-build-deploy/SKILL.md | 17 +++ .../scripts/detect_build_env.sh | 129 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100755 skills/agentbase-build-deploy/scripts/detect_build_env.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b999b7..8ed9773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ review can tell which standard a project was built from. ## Unreleased +- **Deploy: find out how the customer can build before building.** `agentbase-build-deploy` Step 3 now starts with + `scripts/detect_build_env.sh` (read-only; no login, no secret printed): which engine is installed and answering (Docker + Desktop, Rancher Desktop, OrbStack, Colima, Podman, nerdctl, Finch, buildah), `linux/amd64` support, git host and CI config + (GitHub Actions, GitLab CI, Jenkins, Bitbucket, Azure), `gh` / `glab` sign-in; it lists the ways to build and push and the + user picks. Tested here with Docker Desktop and stubs for a hung daemon, nerdctl and a GitLab remote; the nerdctl, Podman, + Finch and GitLab paths are not run against the registry. - **Skill descriptions** of all `agentbase-build-*` skills now open with what the user asks or wants (and say to load the skill before answering, even a short question); `Trigger:` and `DO NOT use` clauses are kept. Haiku 5.5 routing 79% → 95% on the tuning set, Sonnet 5.5 unchanged (98%). The orchestrator says it replaces `/agentbase-wizard` even when that is installed diff --git a/skills/agentbase-build-deploy/SKILL.md b/skills/agentbase-build-deploy/SKILL.md index 9d0e4b1..7c02d07 100644 --- a/skills/agentbase-build-deploy/SKILL.md +++ b/skills/agentbase-build-deploy/SKILL.md @@ -38,6 +38,23 @@ The scaffold commits one **non-secret** config file per environment: `src/backen ## Step 3 — Build the image +**First find out what the user can build with** — do not assume Docker. Run the read-only detector (it changes nothing, logs in nowhere and prints no secret): + +```bash +bash /scripts/detect_build_env.sh +``` + +It reports the container engine that is installed *and answering* (Docker Desktop, Rancher Desktop, OrbStack, Colima, Podman, nerdctl, Finch, buildah), whether it can build `linux/amd64`, the git host and CI config (GitHub Actions, GitLab CI, Jenkins, Bitbucket, Azure), and whether `gh` / `glab` are signed in, then lists the ways to build and push. **Show that list and let the user choose** (a running engine → build locally; GitHub → the CI path below; nothing → install an engine or use CI). + +| What the user has | Build and push | +|---|---| +| Docker, or Rancher Desktop in dockerd mode, OrbStack, Colima, Docker Desktop | The commands below; login with `cr.sh credentials docker-login` | +| Rancher Desktop in containerd mode (`nerdctl`), Podman, Finch | ` build --platform linux/amd64 --build-arg AGENT_VERSION= -t : src/backend`, then ` login`, ` push`. The Makefile targets call `docker`, so these are run by hand. `cr.sh credentials docker-login` only logs `docker` in: the user takes the secret from the Console (or `cr.sh credentials reset`) and enters it into the engine's own prompt, never into chat | +| GitHub | CI path (*CI/CD* below): no local engine needed | +| GitLab, Jenkins, Bitbucket, Azure | No template is shipped. The job needs an engine (or kaniko / buildah), `--platform linux/amd64`, and the registry user / secret as masked CI variables. Same Dockerfile and the same image name / tag rules | + +"Rancher" can also mean the Rancher Kubernetes manager, which does not build images: ask what builds the image there. The nerdctl, Podman, Finch and GitLab rows are the documented commands of those tools and have **not been run against the AgentBase registry** by this repo; only the Docker path has. + ```bash make docker-build IMAGE=// TAG= # AGENT_VERSION= baked in make docker-run IMAGE=... TAG=... # local smoke test: curl :8080/health diff --git a/skills/agentbase-build-deploy/scripts/detect_build_env.sh b/skills/agentbase-build-deploy/scripts/detect_build_env.sh new file mode 100755 index 0000000..8447a78 --- /dev/null +++ b/skills/agentbase-build-deploy/scripts/detect_build_env.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# Read-only check of how this machine / project can BUILD and PUSH the agent image to the registry (vCR): +# which container engine is installed and running (Docker Desktop, Rancher Desktop, OrbStack, Colima, Podman, nerdctl, +# Finch), whether it can build linux/amd64, and which CI the project uses (GitHub Actions, GitLab CI, ...). +# Prints facts and the ways to build; changes nothing, logs in nowhere, prints no credential (only whether a CLI is +# signed in). Usage: bash detect_build_env.sh [project-dir] (default: current directory) +set -u +DIR="${1:-.}" +cd "$DIR" 2>/dev/null || { echo "ERROR: $DIR is not a directory" >&2; exit 1; } + +have() { command -v "$1" >/dev/null 2>&1; } + +# run a command with a time limit (macOS has no `timeout`): a stopped VM makes `docker info` hang. Output goes through a +# file, not a pipe, so a child that outlives the kill cannot keep the caller waiting +limited() { + local secs="$1"; shift + local out; out="$(mktemp)" + "$@" >"$out" 2>/dev/null /dev/null ) >/dev/null 2>&1 & local guard=$! + wait "$pid" 2>/dev/null; local rc=$? + kill "$guard" 2>/dev/null; wait "$guard" 2>/dev/null + cat "$out"; rm -f "$out" + return $rc +} + +ENGINES_READY=() # engines that can build now +echo "== Machine" +ARCH="$(uname -m)" +echo "os/arch: $(uname -s)/$ARCH" +[ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ] && echo "note: ARM host; AgentBase Runtime runs linux/amd64, so build with --platform linux/amd64 (emulated, slower)" + +echo +echo "== Container engines" +if have docker; then + CTX="$(docker context show 2>/dev/null || echo '?')" + INFO="$(limited 15 docker info --format '{{.ServerVersion}}|{{.OperatingSystem}}|{{.Name}}' 2>/dev/null)" + if [ -n "$INFO" ]; then + SRV="${INFO%%|*}"; REST="${INFO#*|}"; OSN="${REST%%|*}"; HOSTN="${REST#*|}" + KIND="Docker Engine" + case "$CTX $OSN $HOSTN" in + *rancher*|*Rancher*|*lima-rancher*) KIND="Rancher Desktop (dockerd / moby mode)" ;; + *orbstack*|*OrbStack*) KIND="OrbStack" ;; + *colima*|*Colima*) KIND="Colima" ;; + *"Docker Desktop"*|*desktop-linux*|*docker-desktop*) KIND="Docker Desktop" ;; + *podman*|*Podman*) KIND="Podman (docker-compatible socket)" ;; + esac + echo "docker: running, $KIND, server $SRV, context '$CTX'" + ENGINES_READY+=("docker") + if docker buildx version >/dev/null 2>&1; then + PLAT="$(limited 15 docker buildx inspect 2>/dev/null | grep -i '^Platforms:' | head -1)" + echo "docker buildx: yes. ${PLAT:-Platforms: unknown}" + case "$PLAT" in *linux/amd64*) ;; *) [ "$ARCH" != "x86_64" ] && echo "warning: linux/amd64 not listed; building for the Runtime may fail (docker run --privileged --rm tonistiigi/binfmt --install amd64)" ;; esac + else + echo "docker buildx: not found (docker build --platform linux/amd64 still works with the classic builder on amd64 hosts)" + fi + else + echo "docker: CLI installed (context '$CTX') but the daemon does not answer within 15 s — start Docker Desktop / Rancher Desktop / OrbStack / Colima first" + fi +else + echo "docker: CLI not found" +fi + +for e in nerdctl podman finch buildah; do + if have "$e"; then + case "$e" in + nerdctl) ok=0; limited 15 nerdctl info >/dev/null 2>&1 && ok=1; extra="(Rancher Desktop in containerd mode, Lima, Colima)" ;; + podman) ok=0; limited 20 podman info >/dev/null 2>&1 && ok=1; extra="(on macOS/Windows: podman machine start)" ;; + finch) ok=0; limited 15 finch info >/dev/null 2>&1 && ok=1; extra="(finch vm start)" ;; + buildah) ok=1; extra="(builds only; push with buildah push, no daemon needed)" ;; + esac + if [ "$ok" = 1 ]; then echo "$e: running $extra"; ENGINES_READY+=("$e"); else echo "$e: installed but not responding $extra"; fi + fi +done +have rdctl && echo "rdctl: Rancher Desktop is installed (container engine: $(rdctl list-settings 2>/dev/null | grep -i -m1 '\"name\"' | sed 's/.*: *//; s/[\",]//g' || echo unknown))" +[ ${#ENGINES_READY[@]} -eq 0 ] && echo "no container engine is ready on this machine" + +echo +echo "== Project and CI" +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + URL="$(git remote get-url origin 2>/dev/null || true)" + # keep only the host: never echo a URL that may embed a token + HOST="$(printf '%s' "$URL" | sed -E 's#^[a-z+]+://([^@/]*@)?##; s#^[^@]+@##; s#[:/].*$##')" + CI_HOST="none" + case "$HOST" in + github.com|*.github.com|*github*) CI_HOST="GitHub" ;; + *gitlab*) CI_HOST="GitLab" ;; + *bitbucket*) CI_HOST="Bitbucket" ;; + dev.azure.com|*visualstudio.com) CI_HOST="Azure DevOps" ;; + "") CI_HOST="none" ;; + *) CI_HOST="other ($HOST)" ;; + esac + echo "git remote host: ${HOST:-no origin remote} → $CI_HOST" +else + echo "not a git repository" + CI_HOST="none" +fi +FOUND=() +for f in .github/workflows/*.yml .github/workflows/*.yaml; do [ -f "$f" ] && { FOUND+=("GitHub Actions (.github/workflows)"); break; }; done +[ -f .gitlab-ci.yml ] && FOUND+=("GitLab CI (.gitlab-ci.yml)") +[ -f Jenkinsfile ] && FOUND+=("Jenkins (Jenkinsfile)") +[ -f bitbucket-pipelines.yml ] && FOUND+=("Bitbucket Pipelines") +[ -f azure-pipelines.yml ] && FOUND+=("Azure Pipelines") +[ ${#FOUND[@]} -gt 0 ] && printf 'CI config in the project: %s\n' "$(IFS=';'; echo "${FOUND[*]}" | sed 's/;/; /g')" || echo "CI config in the project: none" +if have gh; then gh auth status >/dev/null 2>&1 && echo "gh: signed in" || echo "gh: installed, not signed in"; fi +if have glab; then glab auth status >/dev/null 2>&1 && echo "glab: signed in" || echo "glab: installed, not signed in"; fi +[ -f src/backend/Dockerfile ] && echo "Dockerfile: src/backend/Dockerfile (standard)" || { [ -f Dockerfile ] && echo "Dockerfile: ./Dockerfile" || echo "Dockerfile: not found"; } + +echo +echo "== Ways to build and push (pick one with the user; never choose silently)" +N=0 +for e in "${ENGINES_READY[@]+"${ENGINES_READY[@]}"}"; do + N=$((N+1)) + case "$e" in + docker) echo "$N. LOCAL with docker: make docker-build IMAGE=… TAG=… ; bash /cr.sh credentials docker-login ; docker push …" ;; + nerdctl) echo "$N. LOCAL with nerdctl: nerdctl build --platform linux/amd64 -t … src/backend ; nerdctl login ; nerdctl push … (the Makefile targets call docker: run these by hand)" ;; + podman) echo "$N. LOCAL with podman: podman build --platform linux/amd64 -t … src/backend ; podman login ; podman push … (the Makefile targets call docker)" ;; + finch) echo "$N. LOCAL with finch: finch build --platform linux/amd64 -t … src/backend ; finch login ; finch push …" ;; + buildah) echo "$N. LOCAL with buildah: buildah bud --platform linux/amd64 -t … src/backend ; buildah login ; buildah push …" ;; + esac +done +case "$CI_HOST" in + GitHub) N=$((N+1)); echo "$N. CI on GitHub Actions: the scaffold's .github/workflows/ci.yml builds, pushes and deploys (see CI/CD in the skill); no local engine needed" ;; + GitLab) N=$((N+1)); echo "$N. CI on GitLab: the scaffold ships no GitLab template; a job needs docker (or kaniko / buildah) with the registry credentials as masked CI variables" ;; +esac +if [ "$N" -eq 0 ]; then + echo "none available. Install Docker Desktop, Rancher Desktop (dockerd mode), OrbStack, Colima or Podman, or push the project to GitHub and use the CI path." +fi +echo +echo "Registry login: this machine's engine and the CI runner each need credentials for the registry; fetch them with the platform scripts (cr.sh), never paste a secret into chat."