diff --git a/cameleer-runtime-loader/Dockerfile b/cameleer-runtime-loader/Dockerfile new file mode 100644 index 00000000..715026f9 --- /dev/null +++ b/cameleer-runtime-loader/Dockerfile @@ -0,0 +1,14 @@ +# Tiny init-container image. No app code, no shell-injection surface — script +# only sees env vars set by the orchestrator. +FROM busybox:1.37-musl + +# Run as non-root (UID 1000 inside the container; with userns_mode this is +# remapped to host UID ~101000 — fully unprivileged on the host). +RUN adduser -D -u 1000 loader + +COPY entrypoint.sh /usr/local/bin/loader +RUN chmod +x /usr/local/bin/loader + +USER loader +WORKDIR /app +ENTRYPOINT ["/usr/local/bin/loader"] diff --git a/cameleer-runtime-loader/README.md b/cameleer-runtime-loader/README.md new file mode 100644 index 00000000..137a9852 --- /dev/null +++ b/cameleer-runtime-loader/README.md @@ -0,0 +1,19 @@ +# cameleer-runtime-loader + +Init container that fetches the deployable JAR into a shared volume before the +main runtime container starts. Pairs with `DockerRuntimeOrchestrator` / +(future) K8s init-container deploys. + +## Build + + docker build -t gitea.siegeln.net/cameleer/cameleer-runtime-loader: . + docker push gitea.siegeln.net/cameleer/cameleer-runtime-loader: + +## Contract + +- Env: `ARTIFACT_URL` (signed download URL), `ARTIFACT_EXPECTED_SIZE` (bytes). +- Volume: writes `/app/jars/app.jar`. +- Exit 0 on success; non-zero on fetch/size failure. +- Runs as UID 1000 (loader user), drops all caps, read-only rootfs except `/app/jars`. + +See `docs/superpowers/plans/2026-04-27-init-container-jar-fetch.md`. diff --git a/cameleer-runtime-loader/entrypoint.sh b/cameleer-runtime-loader/entrypoint.sh new file mode 100644 index 00000000..2e2043e9 --- /dev/null +++ b/cameleer-runtime-loader/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# cameleer-runtime-loader: fetches one JAR from a signed URL into the shared +# /app/jars/ volume, verifies size, exits. Runs in the same hardened sandbox as +# the main container (cap_drop ALL, read-only rootfs, etc.) — only /app/jars/ +# is writeable. +set -eu + +: "${ARTIFACT_URL:?ARTIFACT_URL is required}" +: "${ARTIFACT_EXPECTED_SIZE:?ARTIFACT_EXPECTED_SIZE is required}" + +OUT=/app/jars/app.jar +mkdir -p /app/jars + +echo "loader: fetching artifact (expected $ARTIFACT_EXPECTED_SIZE bytes)" +# -q quiet, -O output, --tries=3 retry transient network blips, +# --timeout=30 cap stalls. wget exits non-zero on HTTP >=400. +wget -q --tries=3 --timeout=30 -O "$OUT" "$ARTIFACT_URL" + +actual=$(wc -c < "$OUT") +if [ "$actual" -ne "$ARTIFACT_EXPECTED_SIZE" ]; then + echo "loader: size mismatch — expected $ARTIFACT_EXPECTED_SIZE, got $actual" >&2 + exit 2 +fi + +echo "loader: artifact written to $OUT ($actual bytes)"