Add --retry to all Dockerfile curl invocations
Validate / docs-check (push) Successful in 18s
Validate / validate-omos (push) Has been cancelled
Validate / validate-base (push) Has been cancelled
Publish Docker Image / build-base (push) Has been cancelled
Publish Docker Image / build-omos (push) Has been cancelled
Publish Docker Image / update-description (push) Has been cancelled

The first v1.14.29b build attempt failed with an HTTP 502 from
GitHub's release CDN mid-download of zoxide. Single-shot curl had no
retry, so one transient 502 failed the entire OMOS build.

- curl --retry 5 --retry-delay 5 --retry-all-errors on every tool
  download (both -fsSL GETs and -sI HEAD redirect lookups).
- [ -n "$V" ] assertion after each version-resolution step, so a
  failed HEAD lookup fails fast with a clear message instead of
  producing an empty tag that then 404s on the download URL.
- Same hardening applied to the optional Go install block and the
  nodesource setup_22.x pipe.
This commit is contained in:
2026-04-29 10:14:42 +02:00
parent 349bb633ff
commit 2c889b472e
2 changed files with 40 additions and 26 deletions
+6 -2
View File
@@ -8,13 +8,17 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
## v1.14.29b — 2026-04-29
**Fix OMOS `bunx` detection.**
**Fix OMOS `bunx` detection + CI build reliability.**
- **Fix:** `entrypoint-user.sh` checked `command -v bunx` to gate the OMOS auto-install, but the OMOS image only ships the `bun` binary — upstream's bun installer never creates a `bunx` symlink and neither did our Dockerfile. The check always failed on a fresh OMOS image, so `bun x oh-my-opencode-slim@latest install` never ran and first-start OMOS setup would have printed `ENABLE_OMOS=true but bun is not installed.` even though bun was right there. Latent until now because the only exercised path had a persisted `oh-my-opencode-slim.json` from a prior install.
- Changed the gate to `command -v bun`.
- Changed both install invocations from `bunx oh-my-opencode-slim@latest install ...` to `bun x oh-my-opencode-slim@latest install ...`.
- Added `ln -sf bun /usr/local/bin/bunx` to the Dockerfile's OMOS block so interactive users can still type `bunx` by habit, and verified the symlink during build.
- Added `ln -sf bun /usr/local/bin/bunx` to the Dockerfile's OMOS block so interactive users can still type `bunx` by habit, and verified the symlink at build time (`test -L /usr/local/bin/bunx`).
- Smoke test now asserts the `bunx` symlink is present on the OMOS variant.
- **Fix:** CI build robustness against transient GitHub/Gitea CDN failures. The first attempt at building v1.14.29b tripped on a single HTTP 502 from GitHub's release CDN mid-download (`zoxide-0.9.9-x86_64-unknown-linux-musl.tar.gz`), failing the entire OMOS build with no retry. Fix applied to every tool-download curl in the Dockerfile:
- `curl --retry 5 --retry-delay 5 --retry-all-errors` on both the `-fsSL` GET requests and the `-sI` HEAD requests used for `/releases/latest` redirect resolution. 5 attempts with 5 s back-off eats most transient CDN hiccups without failing the build.
- Added `[ -n "$V" ]` assertion after each version-resolution step. If the HEAD redirect ever fails to produce a tag name, the build fails fast with an empty-version message rather than trying to download `.../v//...` and producing a confusing 404.
- Same hardening applied to the optional Go install block (go.dev JSON feed + tarball download) and the nodesource apt-repo setup script.
## v1.14.29 — 2026-04-28
+34 -24
View File
@@ -68,11 +68,12 @@ ARG GOSU_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
V="${GOSU_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/tianon/gosu/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tianon/gosu/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing gosu ${V}" && \
curl -fsSL "https://github.com/tianon/gosu/releases/download/${V}/gosu-${ARCH}" -o /usr/local/bin/gosu && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tianon/gosu/releases/download/${V}/gosu-${ARCH}" -o /usr/local/bin/gosu && \
chmod +x /usr/local/bin/gosu && \
gosu --version
@@ -81,11 +82,12 @@ ARG FZF_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
V="${FZF_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/junegunn/fzf/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/junegunn/fzf/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing fzf ${V}" && \
curl -fsSL "https://github.com/junegunn/fzf/releases/download/v${V}/fzf-${V}-linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin fzf && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/junegunn/fzf/releases/download/v${V}/fzf-${V}-linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin fzf && \
fzf --version
# git-lfs — Git Large File Storage
@@ -93,11 +95,12 @@ ARG GIT_LFS_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
V="${GIT_LFS_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/git-lfs/git-lfs/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/git-lfs/git-lfs/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing git-lfs ${V}" && \
curl -fsSL "https://github.com/git-lfs/git-lfs/releases/download/v${V}/git-lfs-linux-${ARCH}-v${V}.tar.gz" | tar -xz -C /tmp && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/git-lfs/git-lfs/releases/download/v${V}/git-lfs-linux-${ARCH}-v${V}.tar.gz" | tar -xz -C /tmp && \
install /tmp/git-lfs-${V}/git-lfs /usr/local/bin/git-lfs && \
rm -rf /tmp/git-lfs-${V} && \
git lfs install --system && \
@@ -108,11 +111,12 @@ ARG NVIM_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
V="${NVIM_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/neovim/neovim/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/neovim/neovim/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing neovim ${V}" && \
curl -fsSL "https://github.com/neovim/neovim/releases/download/v${V}/nvim-linux-${ARCH}.tar.gz" | tar -xz -C /opt && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/neovim/neovim/releases/download/v${V}/nvim-linux-${ARCH}.tar.gz" | tar -xz -C /opt && \
ln -s /opt/nvim-linux-${ARCH}/bin/nvim /usr/local/bin/nvim && \
nvim --version | head -1
@@ -121,11 +125,12 @@ ARG BAT_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
V="${BAT_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/sharkdp/bat/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/sharkdp/bat/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing bat ${V}" && \
curl -fsSL "https://github.com/sharkdp/bat/releases/download/v${V}/bat-v${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/sharkdp/bat/releases/download/v${V}/bat-v${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
install /tmp/bat-v${V}-${ARCH}-unknown-linux-musl/bat /usr/local/bin/bat && \
rm -rf /tmp/bat-v${V}-* && \
bat --version
@@ -135,11 +140,12 @@ ARG EZA_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
V="${EZA_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/eza-community/eza/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/eza-community/eza/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing eza ${V}" && \
curl -fsSL "https://github.com/eza-community/eza/releases/download/v${V}/eza_${ARCH}-unknown-linux-gnu.tar.gz" | tar -xz -C /usr/local/bin && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/eza-community/eza/releases/download/v${V}/eza_${ARCH}-unknown-linux-gnu.tar.gz" | tar -xz -C /usr/local/bin && \
eza --version | head -1
# zoxide — smarter cd command
@@ -147,11 +153,12 @@ ARG ZOXIDE_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
V="${ZOXIDE_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/ajeetdsouza/zoxide/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/ajeetdsouza/zoxide/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing zoxide ${V}" && \
curl -fsSL "https://github.com/ajeetdsouza/zoxide/releases/download/v${V}/zoxide-${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin zoxide && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/ajeetdsouza/zoxide/releases/download/v${V}/zoxide-${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin zoxide && \
zoxide --version
# uv — fast Python package manager (replaces pip, venv, pyenv)
@@ -160,11 +167,12 @@ ARG UV_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
V="${UV_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://github.com/astral-sh/uv/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/astral-sh/uv/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing uv ${V}" && \
curl -fsSL "https://github.com/astral-sh/uv/releases/download/${V}/uv-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/astral-sh/uv/releases/download/${V}/uv-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
install /tmp/uv-${ARCH}-unknown-linux-musl/uv /usr/local/bin/uv && \
install /tmp/uv-${ARCH}-unknown-linux-musl/uvx /usr/local/bin/uvx && \
rm -rf /tmp/uv-* && \
@@ -198,7 +206,7 @@ RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
# rustup-init -y && source ~/.cargo/env
# Toolchains persist via devbox-rustup and devbox-cargo volumes.
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://static.rust-lang.org/rustup/dist/${ARCH}-unknown-linux-gnu/rustup-init" -o /usr/local/bin/rustup-init && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://static.rust-lang.org/rustup/dist/${ARCH}-unknown-linux-gnu/rustup-init" -o /usr/local/bin/rustup-init && \
chmod +x /usr/local/bin/rustup-init
# gitea-mcp — MCP server for Gitea API (official, Go binary, hosted on gitea.com)
@@ -206,11 +214,12 @@ ARG GITEA_MCP_VERSION=latest
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
V="${GITEA_MCP_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -sI "https://gitea.com/gitea/gitea-mcp/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://gitea.com/gitea/gitea-mcp/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
fi && \
V="${V#v}" && \
[ -n "$V" ] && \
echo "Installing gitea-mcp ${V}" && \
curl -fsSL "https://gitea.com/gitea/gitea-mcp/releases/download/v${V}/gitea-mcp_Linux_${ARCH}.tar.gz" \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://gitea.com/gitea/gitea-mcp/releases/download/v${V}/gitea-mcp_Linux_${ARCH}.tar.gz" \
| tar -xz -C /usr/local/bin/ gitea-mcp && \
chmod +x /usr/local/bin/gitea-mcp && \
gitea-mcp --version
@@ -226,7 +235,7 @@ ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}"
# ── Node.js (required for opencode v1.x install + MCP servers) ──────
ARG NODE_VERSION=22
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \
RUN curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
@@ -241,7 +250,7 @@ RUN ARCH=$(case "${TARGETARCH}" in \
arm64) echo "aarch64" ;; \
*) echo "x86_64" ;; \
esac) && \
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip" -o /tmp/awscli.zip && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip" -o /tmp/awscli.zip && \
unzip -q /tmp/awscli.zip -d /tmp && \
/tmp/aws/install && \
rm -rf /tmp/aws /tmp/awscli.zip && \
@@ -257,11 +266,12 @@ RUN if [ "${INSTALL_GO}" = "true" ]; then \
GOARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
V="${GO_VERSION}" && \
if [ "$V" = "latest" ]; then \
V=$(curl -fsSL "https://go.dev/dl/?mode=json" | \
V=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/?mode=json" | \
awk -F'"' '/"version":/ { sub(/^go/,"",$4); print $4; exit }'); \
fi && \
[ -n "$V" ] && \
echo "Installing Go ${V}" && \
curl -fsSL "https://go.dev/dl/go${V}.linux-${GOARCH}.tar.gz" | tar -C /usr/local -xz && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/go${V}.linux-${GOARCH}.tar.gz" | tar -C /usr/local -xz && \
ln -s /usr/local/go/bin/go /usr/local/bin/go && \
ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \
fi
@@ -280,7 +290,7 @@ RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
elif [ "$ARCH" = "aarch64" ]; then \
BUN_ARCH="aarch64"; \
fi && \
curl -fsSL "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \
unzip -o /tmp/bun.zip -d /tmp/bun && \
mv /tmp/bun/bun-linux-${BUN_ARCH}/bun /usr/local/bin/bun && \
chmod +x /usr/local/bin/bun && \