Both image scripts copied the runtime's installed tree and relied on the sdk inside it being a link into the sibling repository. That is true only where somebody linked them by hand, and false as soon as the dependencies are installed the ordinary way — which fetches the sdk as sources with nothing compiled. The image still built, and every entry point in it pointed at nothing.
101 lines
6.2 KiB
Bash
Executable File
101 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build a per-module runtime image (novox/hq ADR 0052): the tool runtime carrying ONE module's
|
|
# compiled code, which serves that module's tools and runs its events/provisioner under the module's
|
|
# own scoped broker account. Generalises build-runtime-image.sh from the audit-logger to any module.
|
|
#
|
|
# build-module-runtime.sh <module> <output.tar>
|
|
# -> tags mesh-runtime-<module>:development and saves it to <output.tar>
|
|
set -euo pipefail
|
|
|
|
MODULE="${1:?usage: build-module-runtime.sh <module> <output.tar>}"
|
|
OUT="${2:?usage: build-module-runtime.sh <module> <output.tar>}"
|
|
HERE="$(cd "$(dirname "$0")/.." && pwd)"; ROOT="$(cd "$HERE/.." && pwd)"
|
|
MESH_TOOLS="${MESH_TOOLS:-$ROOT/mesh-tools}"
|
|
MESH_SDK="${MESH_SDK:-$ROOT/mesh-sdk}"
|
|
MESH_CATALOG="${MESH_CATALOG:-$ROOT/mesh-catalog}"
|
|
MOD="$MESH_CATALOG/modules/$MODULE"
|
|
TAG="${RUNTIME_TAG:-mesh-runtime-$MODULE:development}"
|
|
BASE="${RUNTIME_BASE:-node:22-bookworm-slim}"
|
|
[ -d "$MOD" ] || { echo "no module $MODULE at $MOD" >&2; exit 1; }
|
|
|
|
( cd "$MESH_SDK" && npm run build >/dev/null )
|
|
( cd "$MESH_TOOLS" && npm run build >/dev/null )
|
|
# Compile whichever of the module's entrypoints exist. Besides the serve-time entrypoints (tools,
|
|
# events, provisioner) and the run-once bootstrap, a module may carry scheduled/one-shot entrypoints
|
|
# it names in a `schedule`/`run-once` container's args (novox/hq ADR 0052/0053) — refresh/apply/usage
|
|
# for the anthropic model-access modules, migrate for model-usage's run-once schema step. tsc pulls
|
|
# in their imports, so leaf files they use are compiled with them. A module may also carry an ambient
|
|
# `.d.ts` typing a third-party dep it default-imports (model-usage's pg.d.ts) — listed here so the
|
|
# ambient declaration is in the program even though the dep is only installed into the image below.
|
|
SRCS=(); for f in \
|
|
client.ts index.ts tools/index.ts provisioner/index.ts bootstrap/index.ts \
|
|
adopt/index.ts refresh/index.ts apply/index.ts usage/index.ts migrate/index.ts \
|
|
pg.d.ts; do
|
|
[ -f "$MOD/$f" ] && SRCS+=("$f")
|
|
done
|
|
TSC="$MESH_SDK/node_modules/.bin/tsc"; ( cd "$MOD" && "$TSC" "${SRCS[@]}" --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist >/dev/null )
|
|
|
|
STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT
|
|
cp -r "$MESH_TOOLS/dist" "$STAGE/dist"
|
|
cp -rL "$MESH_TOOLS/node_modules" "$STAGE/node_modules"
|
|
# And the sdk, from the sibling this script just built, whatever form the installed tree holds it
|
|
# in. It used to be relied on being a symlink into that sibling, which `-L` above materialised —
|
|
# true only on a workstation where somebody had linked them, and false the moment the runtime's
|
|
# dependencies are installed the ordinary way, which now fetches the sdk as sources with nothing
|
|
# compiled in it. The image built then looked fine and every entry point inside it pointed at
|
|
# nothing.
|
|
rm -rf "$STAGE/node_modules/@novox/mesh-sdk"
|
|
mkdir -p "$STAGE/node_modules/@novox"
|
|
cp -rL "$MESH_SDK" "$STAGE/node_modules/@novox/mesh-sdk"
|
|
rm -rf "$STAGE/node_modules/@novox/mesh-sdk/node_modules"
|
|
mkdir -p "$STAGE/modules/$MODULE"; cp -r "$MOD/dist" "$STAGE/modules/$MODULE/dist"
|
|
cp "$MESH_TOOLS/package.json" "$STAGE/package.json"
|
|
|
|
# A module may declare its own third-party runtime deps (the anthropic-manager seals with
|
|
# tweetnacl-sealedbox-js). The shared node_modules copied above carries the common packages and
|
|
# @novox/* — but not a module's private deps. Install those under the module itself, so Node
|
|
# resolves them from /app/modules/<module>/node_modules and still falls back to the shared tree
|
|
# at /app/node_modules for @novox/* and everything common. Modules with no non-@novox deps are a
|
|
# no-op. (@novox/* are workspace deps with no registry to fetch from, so they are excluded here.)
|
|
MOD_DEPS="$(node -e 'const d=(require("'"$MOD"'/package.json").dependencies)||{};process.stdout.write(Object.keys(d).filter(k=>!k.startsWith("@novox/")).map(k=>k+"@"+d[k]).join(" "))')"
|
|
if [ -n "$MOD_DEPS" ]; then
|
|
# shellcheck disable=SC2086
|
|
npm install --prefix "$STAGE/modules/$MODULE" --omit=dev --no-save --no-package-lock --ignore-scripts $MOD_DEPS >/dev/null
|
|
fi
|
|
|
|
# The entrypoints the runtime loads: tools, events and (a provider's) provisioner, whichever exist.
|
|
ENTRIES=""; for e in tools/index.js index.js provisioner/index.js; do
|
|
[ -f "$STAGE/modules/$MODULE/dist/$e" ] && ENTRIES="${ENTRIES:+$ENTRIES,}/app/modules/$MODULE/dist/$e"
|
|
done
|
|
|
|
# A module whose code drives a CLI needs that CLI in the image — postgres shells out to `psql`, minio
|
|
# to `mc`. Everything else speaks a wire protocol or HTTP and needs nothing added.
|
|
EXTRA=""
|
|
case "$MODULE" in
|
|
postgres) EXTRA='RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client && rm -rf /var/lib/apt/lists/*' ;;
|
|
minio) EXTRA='COPY --from=minio/mc:latest /usr/bin/mc /usr/bin/mc' ;;
|
|
# mosquitto drives its dynsec admin — and its run-once bootstrap seeds the store — through
|
|
# `mosquitto_ctrl`. It is not in `mosquitto-clients` on bookworm; the `mosquitto` package carries
|
|
# it (with its shared libraries), and installing from apt keeps them together — copying the binary
|
|
# out of the (musl) eclipse-mosquitto image into this (glibc) base would not load.
|
|
mosquitto) EXTRA='RUN apt-get update && apt-get install -y --no-install-recommends mosquitto && rm -rf /var/lib/apt/lists/*' ;;
|
|
# mongodb's client shells out to `mongosh`. Install it from MongoDB's own apt repo so its shared
|
|
# libraries come with it — copying just the binary out of the mongo image leaves it unable to load.
|
|
mongodb) EXTRA='RUN apt-get update && apt-get install -y --no-install-recommends gnupg curl ca-certificates && curl -fsSL https://pgp.mongodb.com/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb.gpg && echo "deb [signed-by=/usr/share/keyrings/mongodb.gpg] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/7.0 main" > /etc/apt/sources.list.d/mongodb.list && apt-get update && apt-get install -y --no-install-recommends mongodb-mongosh && rm -rf /var/lib/apt/lists/*' ;;
|
|
esac
|
|
|
|
cat > "$STAGE/Dockerfile" <<DOCKER
|
|
FROM $BASE
|
|
WORKDIR /app
|
|
$EXTRA
|
|
COPY package.json ./
|
|
COPY node_modules ./node_modules
|
|
COPY dist ./dist
|
|
COPY modules ./modules
|
|
ENV MESH_TOOL_MODULES=$ENTRIES
|
|
ENTRYPOINT ["node", "dist/main.js"]
|
|
DOCKER
|
|
docker build -t "$TAG" "$STAGE"
|
|
docker save -o "$OUT" "$TAG"
|
|
echo "built $TAG (entrypoints: $ENTRIES) -> $OUT"
|