yams/Dockerfile
Trevon 0dd3985db4
refactor: simplify runtime paths and harden retrieval (#61)
* refactor(tuning): prune dormant runtime knobs

Remove unused tuning controls and adaptive search budget state. Extract testable ONNX slot reconciliation and search telemetry hydration seams, and correct the Apple Silicon sysctl value width.

* refactor(vector): retire dormant backends

Remove unused FAISS, model-infrastructure, VectorIndexManager, and TurboQuant paths while keeping Simeon PQ and float-vector storage.

Migrate unused V2.2 quantized schemas transactionally to V2.3. Databases containing non-default quantized formats or packed-code blobs fail closed with recovery guidance and no mutation.

* refactor(plugins): remove legacy SDK tooling

Remove the Ghidra and SDK submodules, their bespoke tests, and stale scan exclusions. Retain generic PluginProcess coverage against the repository mock fixture.

* build(vector): remove stale FAISS overrides

* refactor(ipc): remove dormant FSM snapshots

* refactor(ingest): unify bounded KG enqueue

* fix(plugins): harden allocation contracts

Keep ABI outputs null or fully initialized when allocations fail, and represent empty object payloads without relying on malloc(0).

* fix(storage): define empty object reads

Return a null buffer with zero length for successful empty-object reads instead of treating malloc(0) as an allocation contract.

* refactor(storage): trim through string views

* test: prune dormant harness helpers

Remove unused readiness and fixture helpers, and keep benchmark client-count doubling in a wide integer domain.

* chore(cpp): initialize analyzer-facing locals

* build(simeon): advance analyzer cleanup

* refactor(config): drop dead daemon client keys

* refactor(ipc): inject daemon client transport

Socket and in-process execution now share DaemonClient request dispatch, so CLI fallbacks do not need alternate service paths.

* refactor(retrieval): route CLI through daemon

Search and grep now use the same daemon handlers over socket or in-process transport. Live grep remains an explicit freshness inspection.

* refactor(graph): move CLI scope into daemon

Graph CLI requests now share daemon handlers across socket and in-process transports. CWD filtering occurs before pagination so scoped totals and pages stay correct.

* build: isolate test-only runtime controls

* refactor(metadata): decouple lock telemetry

* fix(services): return on request timeout

* fix(retrieval): enforce workspace scope

* fix(graph): scope nodes before pagination

* fix(grep): drop binary result lines

* fix(daemon): bound checkpoint wait

* fix(repair): use KG health checks

* fix(storage): always verify compressed CRC

* perf(vector): skip unchanged PQ snapshots

* build(zyp): exclude bench from default build

* build: protect Meson files from formatter

* refactor: remove unreachable production APIs

* refactor(daemon): remove dormant runtime state

* fix(vector): harden daemon provider calls

Timed-out coroutines must own their client and requests. Provider discovery must also fail instead of returning synthetic embeddings in production.

* refactor(storage): remove unusable adapters

* fix(cli): unify in-process add requests

* fix(tsan): preserve async worker progress

* test(cli): isolate daemon-dependent suites

* fix(repair): expose queue backoff progress
2026-07-31 14:16:45 -04:00

301 lines
11 KiB
Docker

# syntax=docker/dockerfile:1.7-labs
# Stage 0: base build dependencies & toolchain
# Using Ubuntu 24.04 for GCC 13+ which supports C++23 <format> header
FROM ubuntu:24.04 AS deps
# FROM debian:trixie-slim AS deps
ARG DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
for i in 1 2 3; do \
apt-get update && \
apt-get install -y --no-install-recommends --fix-missing \
build-essential git curl pkg-config ca-certificates xz-utils \
libssl-dev libsqlite3-dev libsqlite3-0 protobuf-compiler libprotobuf-dev \
libcurl4-openssl-dev \
python3 python3-venv python3-pip \
gcc g++ ninja-build openssl lld llvm clang \
libc++-dev libc++abi-dev \
liburing-dev ccache \
gfortran libomp-18-dev wget \
libopenblas-dev \
cmake && \
rm -rf /var/lib/apt/lists/* && break || \
{ echo "Attempt $i failed, retrying after 5s..."; sleep 5; apt-get clean; rm -rf /var/lib/apt/lists/*; }; \
done
# Install Zig 0.15.2 for zyp PDF plugin (wget -c resumes partial downloads)
ARG ZIG_VERSION=0.15.2
RUN set -eux; \
ARCH=$(uname -m); \
case "$ARCH" in \
x86_64) ZIG_ARCH="x86_64" ;; \
aarch64) ZIG_ARCH="aarch64" ;; \
*) echo "Unsupported architecture: $ARCH"; exit 1 ;; \
esac; \
case "${ZIG_VERSION}" in \
0.15.*) ;; \
*) echo "Unsupported Zig version ${ZIG_VERSION}"; exit 1 ;; \
esac; \
wget -q --show-progress --tries=20 --retry-connrefused -c \
"https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \
-O /tmp/zig.tar.xz && \
tar -xJf /tmp/zig.tar.xz -C /opt && \
mv /opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION} /opt/zig && \
ln -sf /opt/zig/zig /usr/local/bin/zig && \
rm /tmp/zig.tar.xz && \
zig version
# Install Rust toolchain for libsql build (pinned version to avoid cross-device link issues)
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN set -eux; \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.83.0 --profile minimal && \
rustup set auto-self-update disable && \
rustc --version && cargo --version
RUN python3 -m venv /opt/venv \
&& /opt/venv/bin/pip install --upgrade pip \
&& /opt/venv/bin/pip install "conan==2.5.*" "meson==1.4.*" "ninja==1.11.*"
ENV PATH="/opt/venv/bin:${PATH}"
WORKDIR /src
# Stage 1: builder - use setup.sh for consistent build process
FROM deps AS builder
ARG DOCKERFILE_CONF_REV=5
ARG YAMS_VERSION=dev
ARG GIT_COMMIT=""
ARG GIT_TAG=""
ARG BUILD_TESTS=false
ARG BUILD_BENCHMARKS=false
ARG BUILD_DOCS=false
ARG YAMS_CPPSTD=20
ARG BUILD_JOBS=2
COPY . .
# Initialize git submodules (sqlite-vec-cpp, etc.)
RUN git init 2>/dev/null || true && \
git config --global url."https://github.com/".insteadOf "git@github.com:" && \
git submodule update --init --recursive 2>/dev/null || \
echo "Submodule init skipped (not a git repo or no submodules)"
# Use setup.sh with retry logic for Conan remote issues
# Configure Conan to retry downloads more aggressively for transient network failures
RUN --mount=type=cache,target=/root/.conan2 \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/src/subprojects/libsql/target \
set -eux; \
# Prevent rustup from auto-updating when rust-toolchain.toml requests different targets
export RUSTUP_TOOLCHAIN=1.83.0 && \
mkdir -p /root/.conan2 && \
printf 'core.net.http:timeout=120\ncore.net.http:max_retries=10\ncore.download:retry=10\ncore.download:retry_wait=5\n' > /root/.conan2/global.conf && \
conan --version && \
conan profile detect --force && \
echo '=== Conan remotes ==='; conan remote list || true && \
if ! conan remote list | grep -q 'conancenter'; then \
conan remote add conancenter https://center2.conan.io; \
fi && \
conan remote update conancenter --url https://center2.conan.io || true && \
export YAMS_COMPILER=gcc; \
export YAMS_CPPSTD=${YAMS_CPPSTD}; \
export YAMS_EXTRA_MESON_FLAGS="-Drequire-sqlite-vec=false"; \
sed -i 's/\r$//' setup.sh && chmod +x setup.sh && \
for attempt in 1 2 3; do \
echo "=== Build attempt $attempt ==="; \
if [ "${BUILD_TESTS}" = "true" ]; then \
echo "BUILD_TESTS=true: enabling Release tests"; \
SETUP_ARGS="Release --with-tests"; \
else \
SETUP_ARGS="Release"; \
fi; \
# shellcheck disable=SC2086
if ./setup.sh ${SETUP_ARGS}; then \
break; \
else \
echo "setup.sh attempt $attempt failed"; \
if [ $attempt -lt 3 ]; then \
echo "Cleaning cache and retrying after 30s..."; \
rm -rf /src/build/release; \
rm -rf /root/.conan2/p/*/metadata.json 2>/dev/null || true; \
conan cache clean "*" || true; \
sleep 30; \
else \
echo "All attempts failed"; \
exit 1; \
fi; \
fi; \
done; \
CONAN_BUILD_ENV="/src/build/release/build-release/conan/conanbuild.sh"; \
if [ -f "${CONAN_BUILD_ENV}" ]; then set +u; . "${CONAN_BUILD_ENV}"; set -u; fi && \
meson compile -C build/release -j${BUILD_JOBS} && \
meson install -C build/release --destdir /opt/yams && \
# Ensure Conan-provided runtime libs are present as real files (not only symlinks)
# so that Meson tests can run in later stages without access to /root/.conan2.
python3 - <<'PY'
import glob
import os
import platform
import shutil
import re
destinations = ['/src/build/release', '/opt/yams/usr/local/lib']
for dst in destinations:
os.makedirs(dst, exist_ok=True)
copied = set()
created_links = set()
elf_machine_codes = {
'x86_64': 0x3E,
'aarch64': 0xB7,
'arm64': 0xB7,
}
expected_machine = elf_machine_codes.get(platform.machine().lower())
def elf_machine(path: str):
try:
with open(path, 'rb') as fh:
header = fh.read(20)
except OSError:
return None
if len(header) < 20 or header[:4] != b'\x7fELF':
return None
endian = header[5]
if endian == 1:
return int.from_bytes(header[18:20], 'little')
if endian == 2:
return int.from_bytes(header[18:20], 'big')
return None
for pat in [
'/src/build/release/**/libtbb*.so*',
'/src/build/release/**/libonnxruntime*.so*',
'/root/.conan2/p/**/p/lib/libtbb*.so*',
'/root/.conan2/p/**/p/lib/libtbbbind*.so*',
'/root/.conan2/p/**/p/lib/libonnxruntime*.so*',
'/root/.conan2/p/**/lib/libtbb*.so*',
'/root/.conan2/p/**/lib/libtbbbind*.so*',
'/root/.conan2/p/**/lib/libonnxruntime*.so*',
]:
for p in glob.glob(pat, recursive=True):
base = os.path.basename(p)
rp = os.path.realpath(p)
if not os.path.isfile(rp):
continue
machine = elf_machine(rp)
if expected_machine is not None and machine is not None and machine != expected_machine:
continue
real_base = os.path.basename(rp)
if real_base not in copied:
for dst in destinations:
dest_path = os.path.join(dst, real_base)
if not (os.path.exists(dest_path) and os.path.samefile(rp, dest_path)):
shutil.copy2(rp, dest_path)
copied.add(real_base)
if base != real_base:
for dst in destinations:
link_path = os.path.join(dst, base)
if not os.path.lexists(link_path):
os.symlink(real_base, link_path)
created_links.add((dst, base))
def ensure_soname_links(directory: str) -> int:
created = 0
# If we have libfoo.so.M.N or libfoo.so.M.N.K, create libfoo.so.M -> that file.
rx = re.compile(r'^(?P<stem>lib.+\.so\.(?P<maj>\d+))\.(?P<rest>\d+(?:\.\d+)*)$')
for name in os.listdir(directory):
m = rx.match(name)
if not m:
continue
target = name
link_name = m.group('stem')
link_path = os.path.join(directory, link_name)
if os.path.lexists(link_path):
continue
os.symlink(target, link_path)
created += 1
return created
links = sum(ensure_soname_links(dst) for dst in destinations)
print(
f'Copied {len(copied)} runtime libs into {", ".join(destinations)}; '
f'preserved {len(created_links)} package symlinks; created {links} SONAME symlinks'
)
PY
RUN set -eux; \
bash scripts/prune-runtime-install.sh /opt/yams/usr/local
# Stage 1.25: smoke tests (optional)
# Note: kept separate so Linux builder images can be produced even when
# unrelated tests are failing.
FROM builder AS smoke-tests
ARG BUILD_TESTS=false
ARG BUILD_JOBS=2
RUN set -eux; \
if [ "${BUILD_TESTS}" != "true" ]; then \
echo "BUILD_TESTS=false: skipping smoke tests"; \
exit 0; \
fi; \
# Conan runtime_deploy copies shared libs into build dir; add it to loader path.
export LD_LIBRARY_PATH="/opt/yams/usr/local/lib:/src/build/release:${LD_LIBRARY_PATH:-}"; \
meson test -C build/release -j${BUILD_JOBS} --no-rebuild --print-errorlogs mcp_submodule cli_submodule
# Stage 1.3: full test suite (optional)
FROM builder AS tests
ARG BUILD_TESTS=false
ARG BUILD_JOBS=2
RUN set -eux; \
if [ "${BUILD_TESTS}" != "true" ]; then \
echo "BUILD_TESTS=false: skipping full test suite"; \
exit 0; \
fi; \
export LD_LIBRARY_PATH="/opt/yams/usr/local/lib:/src/build/release:${LD_LIBRARY_PATH:-}"; \
meson test -C build/release -j${BUILD_JOBS} --no-rebuild --print-errorlogs
# Stage 1.5: runtime self-test (fails the build if runtime deps are missing)
FROM builder AS runtime-test
RUN set -eux; \
export LD_LIBRARY_PATH="/opt/yams/usr/local/lib:${LD_LIBRARY_PATH:-}"; \
/opt/yams/usr/local/bin/yams --version >/dev/null; \
if [ -x /opt/yams/usr/local/bin/yams-mcp-server ]; then /opt/yams/usr/local/bin/yams-mcp-server --help >/dev/null; fi
FROM debian:trixie-slim AS runtime
ARG DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
for i in 1 2 3; do \
apt-get update && \
apt-get install -y --no-install-recommends --fix-missing ca-certificates liburing2 && \
rm -rf /var/lib/apt/lists/* && break || \
{ echo "Attempt $i failed, retrying after 5s..."; sleep 5; apt-get clean; rm -rf /var/lib/apt/lists/*; }; \
done
RUN groupadd -r yams && useradd -r -g yams -s /bin/false yams
COPY --from=builder /opt/yams /opt/yams
ENV YAMS_PREFIX=/opt/yams/usr/local
ENV PATH="${YAMS_PREFIX}/bin:${PATH}"
ENV LD_LIBRARY_PATH="${YAMS_PREFIX}/lib:${LD_LIBRARY_PATH}"
# Backward compatibility: retain standalone yams symlink
RUN ln -sf ${YAMS_PREFIX}/bin/yams /usr/local/bin/yams && \
if [ -f ${YAMS_PREFIX}/bin/yams-daemon ]; then ln -sf ${YAMS_PREFIX}/bin/yams-daemon /usr/local/bin/yams-daemon; fi && \
mkdir -p /usr/local/share/yams/data
# Copy data files if they exist (optional)
# Note: Disabled because /src/data/magic_numbers.json doesn't exist yet
# RUN --mount=type=bind,from=builder,source=/src/data,target=/tmp/data \
# if [ -f /tmp/data/magic_numbers.json ]; then \
# cp /tmp/data/magic_numbers.json /usr/local/share/yams/data/; \
# fi
RUN mkdir -p /home/yams/.local/share/yams /home/yams/.config/yams && chown -R yams:yams /home/yams
RUN mknod /dev/tty c 5 0 2>/dev/null || true && \
chmod 666 /dev/tty 2>/dev/null || true
USER yams
WORKDIR /home/yams
ENV YAMS_STORAGE="/home/yams/.local/share/yams" \
XDG_DATA_HOME="/home/yams/.local/share" \
XDG_CONFIG_HOME="/home/yams/.config"
LABEL org.opencontainers.image.description="YAMS server & daemon. Run 'yams serve' for MCP or 'yams-daemon' for socket daemon."
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD yams --version || exit 1
ENTRYPOINT ["yams"]
CMD ["--help"]