diff --git a/.github/setup-runner.sh b/.github/setup-runner.sh index 563b492b7..7abd2226a 100755 --- a/.github/setup-runner.sh +++ b/.github/setup-runner.sh @@ -57,7 +57,7 @@ fi # ── Seed local Docker registry ─────────────────────────────────────────── # (Run before Python/Playwright so failures there don't block image seeding) echo "Pushing CI images to local Docker registry (${DOCKER_REGISTRY})..." -ELIXIR_IMAGE="hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241202-slim" +ELIXIR_IMAGE="hexpm/elixir:1.17.3-erlang-27.3.4.14-debian-bookworm-20260623-slim" RUNNER_IMAGE="debian:bookworm-20241202-slim" for img in postgres:18.4 qdrant/qdrant:v1.17.1 node:20-slim "$ELIXIR_IMAGE" "$RUNNER_IMAGE"; do local_tag="${DOCKER_REGISTRY}/${img}" diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index bfad5bc5b..ce802daaa 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -557,7 +557,7 @@ jobs: # GitHub Actions rejects the `env` context inside a job-level `env:` # block, even referencing a workflow-level var (fails the whole run at # parse time with zero jobs created, "workflow file issue"). - MIX_BUILDER_IMAGE: "hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241202-slim" + MIX_BUILDER_IMAGE: "hexpm/elixir:1.17.3-erlang-27.3.4.14-debian-bookworm-20260623-slim" outputs: beam-tag: ${{ steps.beam.outputs.tag }} deps-key: ${{ steps.keys.outputs.deps }} diff --git a/Dockerfile b/Dockerfile index 6fde90892..1f93070f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,8 @@ # Multi-stage build: compile release in builder, run in minimal image # cache-bust: 2026-04-18-encryption-auto-provision ARG ELIXIR_VERSION=1.17.3 -ARG OTP_VERSION=27.1.2 -ARG DEBIAN_VERSION=bookworm-20241202-slim +ARG OTP_VERSION=27.3.4.14 +ARG DEBIAN_VERSION=bookworm-20260623-slim ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" diff --git a/ci/compose.database.yml b/ci/compose.database.yml index 31fd69292..efbd7d1b0 100644 --- a/ci/compose.database.yml +++ b/ci/compose.database.yml @@ -21,7 +21,7 @@ services: # Bare image refs; runner's HTTPS_PROXY config caches via FastRaid # transparently (see docs/context/runner-vm-setup.md Gotcha #6). NODE_IMAGE: node:20-slim - BUILDER_IMAGE: hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241202-slim + BUILDER_IMAGE: hexpm/elixir:1.17.3-erlang-27.3.4.14-debian-bookworm-20260623-slim RUNNER_IMAGE: debian:bookworm-20241202-slim ports: - "${CI_ENGRAM_PORT:-8110}:4000" diff --git a/ci/compose.yml b/ci/compose.yml index eecc3c3c0..9602e5729 100644 --- a/ci/compose.yml +++ b/ci/compose.yml @@ -33,7 +33,7 @@ services: # ${LOCAL_REGISTRY} treats the proxy as a registry mirror, which it is # not, and BuildKit fails with "unexpected media type / not found". NODE_IMAGE: node:20-slim - BUILDER_IMAGE: hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241202-slim + BUILDER_IMAGE: hexpm/elixir:1.17.3-erlang-27.3.4.14-debian-bookworm-20260623-slim RUNNER_IMAGE: debian:bookworm-20241202-slim # LAN Hex pull-through (set by CI env; empty for local dev = upstream). HEX_MIRROR: ${HEX_MIRROR:-} diff --git a/docs/context/ci-mix-compile-cache-runner-path.md b/docs/context/ci-mix-compile-cache-runner-path.md index 4e793eceb..fa650b2ba 100644 --- a/docs/context/ci-mix-compile-cache-runner-path.md +++ b/docs/context/ci-mix-compile-cache-runner-path.md @@ -53,7 +53,7 @@ Requires `fetch-depth: 0` on the checkout step — shallow clone (`fetch-depth: **2. Docker-wrap the compile** (the fix that actually matters — addresses the absolute-path invalidation): -Run `mix local.hex`/`mix local.rebar`/`mix deps.get`/`mix deps.compile`/`mix compile` inside `MIX_BUILDER_IMAGE` (the same `hexpm/elixir:1.17.3-erlang-27.1.2-debian-bookworm-20241202-slim` the release Dockerfile pins), bind-mounted via `docker run -v "$PWD:/app" -w /app`. A bind mount is a real mountpoint (unlike a symlink, which Erlang's `getcwd()` resolves through to the real, unstable path) — `/app` resolves identically via `getcwd()` on every runner, so the compile manifest's embedded project root is always the same string regardless of which of the 7 runners lands the job. This is exactly the mechanism that already makes `prebuild-ci-image` immune to this bug. +Run `mix local.hex`/`mix local.rebar`/`mix deps.get`/`mix deps.compile`/`mix compile` inside `MIX_BUILDER_IMAGE` (the same `hexpm/elixir:1.17.3-erlang-27.3.4.14-debian-bookworm-20260623-slim` the release Dockerfile pins), bind-mounted via `docker run -v "$PWD:/app" -w /app`. A bind mount is a real mountpoint (unlike a symlink, which Erlang's `getcwd()` resolves through to the real, unstable path) — `/app` resolves identically via `getcwd()` on every runner, so the compile manifest's embedded project root is always the same string regardless of which of the 7 runners lands the job. This is exactly the mechanism that already makes `prebuild-ci-image` immune to this bug. **Reference the bare Docker Hub tag, not the `:5001` LAN registry mirror.** The `:5001` `LOCAL_REGISTRY` only actually holds the `engram-ci` images this workflow builds and pushes itself — `hexpm/elixir` was never pushed there despite `.github/setup-runner.sh` appearing to do so (confirmed live: pulling `10.0.20.214:5001/hexpm/elixir:...` failed with `manifest unknown`). Docker Hub pulls on this runner pool already transparently route through a caching proxy at `:5000` (`docs/context/runner-vm-setup.md` Gotcha #6), so the bare tag is both correct and already cache-accelerated — it's the same tag `prebuild-ci-image` pulls successfully via the Dockerfile's own `BUILDER_IMAGE` arg. diff --git a/mix.exs b/mix.exs index 8be52cf4d..32561ad8f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Engram.MixProject do def project do [ app: :engram, - version: "0.5.649", + version: "0.5.651", elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/rel/env.sh.eex b/rel/env.sh.eex index 358b943ca..da6c9c23d 100644 --- a/rel/env.sh.eex +++ b/rel/env.sh.eex @@ -20,7 +20,21 @@ if [ -n "$ECS_ENABLE_CLUSTER" ]; then # first on a dual-stack host, and `hostname -I` also lists the 169.254.x # ECS task-metadata address — both would break node-name matching. Take the # first non-link-local IPv4 from the full address list instead. - _eni_ipv4="$(hostname -I | awk '{for (i = 1; i <= NF; i++) if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && $i !~ /^169\.254\./) { print $i; exit }}')" + # Under boot pressure `hostname -I` can transiently return nothing (#966: + # tasks booted undistributed during the Jul 3 churn window). Retry briefly, + # then fail loudly — an undistributed boot is invisible forever, a failed + # boot gets replaced by ECS. + _eni_ipv4="" + for _try in 1 2 3 4 5; do + _eni_ipv4="$(hostname -I | awk '{for (i = 1; i <= NF; i++) if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && $i !~ /^169\.254\./) { print $i; exit }}')" + [ -n "$_eni_ipv4" ] && break + echo "rel/env.sh: no ENI IPv4 yet (attempt ${_try}/5), retrying" >&2 + sleep 1 + done + if [ -z "$_eni_ipv4" ]; then + echo "rel/env.sh: FATAL: no ENI IPv4 for RELEASE_NODE; refusing undistributed boot" >&2 + exit 1 + fi export RELEASE_NODE="engram@${_eni_ipv4}" fi