diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c56d5184..b491ed0d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -66,6 +66,18 @@ jobs: test -s /tmp/user/hello.1.so ' + # The repl entrypoint mode (#538): pipe an expression into + # `docker run -i ... repl` (its own in-container orlyi; no port mapping + # needed, and no clash with the container above) and assert the value + # comes back. Gates node in the runtime stage, the baked orly-repl + # build, and the entrypoint's background-orlyi handshake at once. + - name: REPL mode against the image + run: | + printf '40 + 2;\n:quit\n' \ + | docker run -i --rm ghcr.io/orlyatomics/orly:ci repl \ + | tee /tmp/repl-out + grep -qE '(^|orly> )42$' /tmp/repl-out + - name: Container logs (for diagnosis) if: always() run: docker logs orly 2>&1 | tail -60 diff --git a/Dockerfile b/Dockerfile index 4c4144dd..0d8d3d54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,9 @@ # # The WebSocket + JSON protocol is then on ws://127.0.0.1:8082/ -- point any # client driver (clients/{python,go,ts}) or the MCP server (clients/mcp) at it. +# +# Interactive orlyscript (#538): `docker run -it --rm ghcr.io/orlyatomics/orly repl` +# starts orlyi inside the container and drops straight into orly-repl. # ---- build stage ----------------------------------------------------------- @@ -15,6 +18,7 @@ FROM ubuntu:24.04 AS build RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential gcc g++ uuid-dev libgmp-dev libaio-dev libsnappy-dev \ libreadline-dev libboost-system-dev zlib1g-dev bison flex python3 \ + nodejs npm \ && rm -rf /var/lib/apt/lists/* WORKDIR /src/orly @@ -41,6 +45,15 @@ RUN mkdir -p /opt/orly-packages /tmp/pkgout && touch /opt/orly-packages/__orly__ && /src/out_orly/release/orly/orlyc -o /tmp/pkgout /src/orly/examples/prediction-market/market.orly \ && cp /tmp/pkgout/*.so /opt/orly-packages/ +# Build the TS driver + orly-repl (#538); they reach the runtime stage inside +# the /src/orly -> /opt/orly/src copy, so prune dev deps (typescript et al.) +# down to what `node dist/index.js` needs. Order matters: the repl's npm +# install copies clients/ts (per its `files`), so ts builds first. +RUN cd /src/orly/clients/ts && npm install --silent && npx tsc \ + && cd /src/orly/clients/repl && npm install --silent && npx tsc \ + && npm prune --omit=dev \ + && cd /src/orly/clients/ts && npm prune --omit=dev + # ---- runtime stage --------------------------------------------------------- FROM ubuntu:24.04 @@ -54,9 +67,10 @@ LABEL org.opencontainers.image.source="https://github.com/orlyatomics/orly" \ # package compiles need: g++ (orlyc execs `g++ -I$ORLY_SRC_ROOT ...`) and # uuid-dev (generated code includes base/uuid.h -> ; the lib # alone is not enough -- verified by compiling a package in the container). +# nodejs (no npm) runs the pre-built orly-repl. RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ g++ uuid-dev libgmp10 libgmpxx4ldbl libaio1t64 libboost-system1.83.0 \ - libreadline8t64 libsnappy1v5 zlib1g \ + libreadline8t64 libsnappy1v5 zlib1g nodejs \ && rm -rf /var/lib/apt/lists/* COPY --from=build /src/out_orly/release/orly/server/orlyi /usr/local/bin/orlyi @@ -65,6 +79,11 @@ COPY --from=build /src/out_orly/release/orly/orlyc /usr/local/bin/orlyc COPY --from=build /src/orly /opt/orly/src COPY --from=build /opt/orly-packages /var/lib/orly/packages COPY docker/entrypoint.sh /usr/local/bin/orly-entrypoint +# Shim so `docker exec -it orly-repl ...` works against a running +# default-entrypoint container, not just the `repl` entrypoint mode. +RUN printf '#!/bin/sh\nexec node /opt/orly/src/clients/repl/dist/index.js "$@"\n' \ + > /usr/local/bin/orly-repl \ + && chmod +x /usr/local/bin/orly-repl # Trailing slash matters: the compiler concatenates paths onto this directly. ENV ORLY_SRC_ROOT=/opt/orly/src/ diff --git a/README.md b/README.md index 03b5803e..b642cd3f 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,13 @@ image, so it compiles your own `.orly` packages too `docker run ... ` pass straight through to `orlyi` ([#530](https://github.com/orlyatomics/orly/issues/530)). +Or skip the client entirely and type orlyscript at it +([#538](https://github.com/orlyatomics/orly/issues/538)): + +```sh +docker run -it --rm ghcr.io/orlyatomics/orly repl +``` + **From source** — system dependencies (Ubuntu 24.04): ```sh diff --git a/changelog.d/538-docker-repl-mode.md b/changelog.d/538-docker-repl-mode.md new file mode 100644 index 00000000..05084de1 --- /dev/null +++ b/changelog.d/538-docker-repl-mode.md @@ -0,0 +1 @@ +- **Added**: the docker image now bakes in `orly-repl` — `docker run -it --rm ghcr.io/orlyatomics/orly repl` starts the in-container orlyi and drops straight into an orlyscript prompt (zero host setup; flags after `repl` pass to the REPL, and an `orly-repl` shim on PATH serves `docker exec` against a default-entrypoint container). PR-gated in the docker workflow by piping an expression through the published entrypoint (#538). diff --git a/clients/repl/README.md b/clients/repl/README.md index c3012c37..c141ed8f 100644 --- a/clients/repl/README.md +++ b/clients/repl/README.md @@ -44,7 +44,15 @@ Commands at the prompt: `:help`, `:defs`, `:drop `, `:src` (show the synth ## Against the docker image -The image ships `orlyc` but the compile/install loop needs a package directory both sides can see, so start the container with one mounted at an identical path, and run `orlyc` inside the container: +The REPL is baked into the image ([#538](https://github.com/orlyatomics/orly/issues/538)), so the whole thing is one line — orlyi starts inside the container and the prompt appears when it's ready: + +```sh +docker run -it --rm ghcr.io/orlyatomics/orly repl +``` + +Flags after `repl` go to orly-repl (e.g. `repl --shared`). Against an already-running default-entrypoint container, use the shim: `docker exec -it orly-repl --package-dir /var/lib/orly/packages`. + +If you'd rather run the REPL on the host against a containerized server (say, to join a pov your host-side agents are using), the compile/install loop needs a package directory both sides can see at the same path, and `orlyc` runs inside the container: ```sh mkdir -p /tmp/orly-repl/packages && touch /tmp/orly-repl/packages/__orly__ @@ -55,8 +63,6 @@ npx tsc && node dist/index.js \ --orlyc "docker exec orly orlyc" ``` -(Compiled `.so` paths land in the shared mount, so the container-side `orlyc` and the host-side REPL agree on where everything is.) - ## Building and joining agents `orly-repl --pov ...` attaches to a POV your agents (e.g. via [`clients/mcp`](../mcp)) are already writing — the REPL then reads and writes the same live state, which makes it a debugger for agent shared memory. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index ef705d23..c1861660 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -3,23 +3,42 @@ # example packages (sample, graph, market) installable out of the box, WS on # 8082. Extra `docker run ... ` are appended verbatim, so any orlyi # flag can be overridden or added. Sizing knobs via env for the common case. +# +# `docker run -it ... repl [orly-repl flags]` (#538) instead starts that same +# orlyi in the background (logs to /var/log/orly/orlyi.log) and drops into +# orly-repl pointed at it; when the REPL exits, the container exits. set -e -exec orlyi \ - --mem_sim \ - --mem_sim_mb="${ORLY_MEM_SIM_MB:-256}" \ - --mem_sim_slow_mb="${ORLY_MEM_SIM_SLOW_MB:-64}" \ - --create=true \ - --instance_name="${ORLY_INSTANCE_NAME:-orly}" \ - --starting_state=SOLO \ - --port_number=8080 \ - --slave_port_number=8081 \ - --ws_port_number=8082 \ - --reporting_port_number=8083 \ - --connection_backlog=32 \ - --package_dir=/var/lib/orly/packages \ - --max_parallel_frames 4000 \ - --page_cache_size 256 \ - --block_cache_size 64 \ - --le --log_info \ - "$@" +orlyi_args=( + --mem_sim + --mem_sim_mb="${ORLY_MEM_SIM_MB:-256}" + --mem_sim_slow_mb="${ORLY_MEM_SIM_SLOW_MB:-64}" + --create=true + --instance_name="${ORLY_INSTANCE_NAME:-orly}" + --starting_state=SOLO + --port_number=8080 + --slave_port_number=8081 + --ws_port_number=8082 + --reporting_port_number=8083 + --connection_backlog=32 + --package_dir=/var/lib/orly/packages + --max_parallel_frames 4000 + --page_cache_size 256 + --block_cache_size 64 + --le --log_info +) + +if [ "$1" = "repl" ]; then + shift + mkdir -p /var/log/orly + orlyi "${orlyi_args[@]}" > /var/log/orly/orlyi.log 2>&1 & + for _ in $(seq 1 60); do + if (echo > /dev/tcp/127.0.0.1/8082) 2>/dev/null; then break; fi + sleep 1 + done + export ORLY_URL="ws://127.0.0.1:8082/" + exec node /opt/orly/src/clients/repl/dist/index.js \ + --package-dir /var/lib/orly/packages "$@" +fi + +exec orlyi "${orlyi_args[@]}" "$@"