Tier-1 online collaboration: an owner grants accounts into their tenant's ACL (grant/revoke/list under /api/share/), and a visitor addresses it per request with ?tenant=<ownerId> — query rather than header because EventSource cannot set headers, and share routes always administer the caller's own tenant so a grant is not re-shareable. The wasm shell threads the parameter through its four XHR helpers and the event stream in one place. A shared tenant has no collaboration session, so the 409 auto-resolve now also accepts when the daemon advertises serveMode online (the daemon's counter is the total order and SSE is already delivering the newer document; the old latch would freeze a visitor permanently). Evicted tenants persist document + ACL under sha256(user_id) directories via atomic writes — written before the registry remove so no instant lacks both copies; unwritable tenants stay resident and unloadable files are set aside as .corrupt rather than overwritten. Also restores the Dockerfile entrypoint literal the M2 CMD restructure dropped (its CI test only ran in the workspace suite) and splits live_sync_glue under the file cap.
171 lines
8.7 KiB
Docker
171 lines
8.7 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# Dockerfile.web-rust — container for the RUST web host (C1).
|
|
#
|
|
# Serves the Rust CanvasKit web editor: the `op-host-web-server` daemon running
|
|
# in `--serve-web` mode, hosting the wasm-bindgen bundle + the vendored
|
|
# CanvasKit artifact out of a `web-bundle/` directory placed next to the binary.
|
|
# This is the sole web container now that the TypeScript/Nitro web app (and its
|
|
# root `Dockerfile`) have been retired.
|
|
#
|
|
# Build (self-contained, builds the wasm bundle in-image — the default):
|
|
# docker build -f Dockerfile.web-rust -t openpencil-web-rust .
|
|
#
|
|
# Build reusing a prebuilt bundle (the `op-web-bundle` artifact from the
|
|
# `wasm-bundle-build.yml` CI job — skips the in-image wasm rebuild):
|
|
# # download + unzip the CI artifact into ./web-bundle first, then:
|
|
# docker build -f Dockerfile.web-rust --build-arg WEB_BUNDLE_SOURCE=copy \
|
|
# -t openpencil-web-rust .
|
|
#
|
|
# Run (single-user, the historical default):
|
|
# docker run -p 3100:3100 openpencil-web-rust
|
|
# # then open http://localhost:3100/
|
|
#
|
|
# Run (multi-account online mode, behind a reverse proxy on one public origin):
|
|
# docker run -p 3100:3100 \
|
|
# -e OPENPENCIL_SERVE_MODE=online \
|
|
# -e OPENPENCIL_HUB_BASE_URL=http://backend:8080 \
|
|
# -e OPENPENCIL_HUB_INTERNAL_AUTH="$(cat /run/secrets/op-canvas-internal-auth)" \
|
|
# -e OPENPENCIL_WEB_ALLOWED_ORIGINS=https://hub.example.com \
|
|
# openpencil-web-rust
|
|
# Every request is then attributed to a verified op-hub account and served
|
|
# against that account's own document. See `web_canvas_server/online_policy.rs`
|
|
# for what online mode refuses, and `hub_auth_client.rs` for the identity call.
|
|
#
|
|
# Why wasm-in-Docker is the default (vs. always COPY-artifact): the production
|
|
# `canvaskit` feature is pure Rust + web_sys + serde — it needs NO emscripten /
|
|
# EMSDK / skia-safe / libc shim (the from-scratch skia path that needed those
|
|
# was retired 2026-06-17; the editor renders through the official CanvasKit
|
|
# skia WASM loaded separately). So the only extra toolchain over a normal cargo
|
|
# build is the wasm32 target + wasm-bindgen-cli + pinned Binaryen (wasm-opt) +
|
|
# node, all installed in the builder stage. That keeps the image self-contained
|
|
# and reproducible. The `WEB_BUNDLE_SOURCE=copy` build arg is the
|
|
# fast path: it skips the wasm rebuild and copies a `./web-bundle` provided as
|
|
# build context (e.g. the Task-1 CI artifact).
|
|
|
|
# ── Stage 1: builder ──────────────────────────────────────────────────────────
|
|
# rust:1.94 ships cargo + a Debian (bookworm) base. Debian's apt binaryen is
|
|
# too old for rustc 1.94's wasm feature set, so wasm-opt is pinned below to the
|
|
# same modern Binaryen release used by the wasm bundle CI.
|
|
FROM rust:1.94-bookworm AS builder
|
|
|
|
# WEB_BUNDLE_SOURCE = build -> build the wasm bundle in this stage (default).
|
|
# = copy -> skip the build; the runtime stage COPYs a
|
|
# prebuilt ./web-bundle from the build context.
|
|
ARG WEB_BUNDLE_SOURCE=build
|
|
ARG BINARYEN_VERSION=version_123
|
|
|
|
# Port baked into the image's CMD; overridable at build + run time.
|
|
ARG SERVE_PORT=3100
|
|
|
|
# pinned Binaryen -> wasm-opt -Oz ; nodejs -> the 0-env-import assert in the gate
|
|
# script. op-host-web-server is the headless raster daemon (links op-host-services
|
|
# only — no winit/glutin/skia-GL under Approach Y), so NONE of the GL/X11
|
|
# link-time libs the desktop binary needed are required here; only
|
|
# freetype/fontconfig (+ CJK fonts) for skia's raster text shaping at export.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
curl \
|
|
nodejs \
|
|
gzip \
|
|
ca-certificates \
|
|
pkg-config \
|
|
libfreetype-dev libfontconfig1-dev fonts-noto-cjk \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& curl -fsSL \
|
|
"https://github.com/WebAssembly/binaryen/releases/download/${BINARYEN_VERSION}/binaryen-${BINARYEN_VERSION}-x86_64-linux.tar.gz" \
|
|
| tar -xz -C /opt \
|
|
&& ln -s "/opt/binaryen-${BINARYEN_VERSION}/bin/wasm-opt" /usr/local/bin/wasm-opt \
|
|
&& wasm-opt --version
|
|
|
|
WORKDIR /src
|
|
|
|
# Copy the whole repo (the `.dockerignore` trims node_modules / out / dist etc.)
|
|
# Submodules (vendor/jian) must already be checked out in the build context —
|
|
# the CI checkout uses `submodules: recursive`.
|
|
COPY . .
|
|
|
|
# Build the headless web/MCP server binary (the `--serve-web` host) — links
|
|
# op-host-services only, no winit/skia-GL. Always built; this is the runtime
|
|
# binary regardless of how the web bundle is produced.
|
|
RUN cargo build -p op-host-web-server --release
|
|
|
|
# Build the canvaskit wasm bundle in-image, UNLESS WEB_BUNDLE_SOURCE=copy.
|
|
# Mirrors `tools/check-wasm-bundle.sh` exactly (cargo build --features canvaskit
|
|
# -> wasm-bindgen --target web -> 0-env-import assert -> wasm-opt -Oz -> gzip
|
|
# size gate). The script reads the locked wasm-bindgen-cli version requirement
|
|
# from Cargo.lock so the CLI matches the linked runtime.
|
|
RUN if [ "$WEB_BUNDLE_SOURCE" = "build" ]; then \
|
|
rustup target add wasm32-unknown-unknown && \
|
|
version="$(awk '/^name = "wasm-bindgen"$/{found=1; next} found && /^version = /{gsub(/[" ]/,"",$3); print $3; exit}' Cargo.lock)" && \
|
|
if [ -z "$version" ]; then echo "could not resolve wasm-bindgen version from Cargo.lock" >&2; exit 2; fi && \
|
|
cargo install wasm-bindgen-cli --version "$version" --locked && \
|
|
bash tools/check-wasm-bundle.sh ; \
|
|
else \
|
|
echo "WEB_BUNDLE_SOURCE=$WEB_BUNDLE_SOURCE — skipping in-image wasm build; runtime stage will COPY ./web-bundle from the build context" ; \
|
|
fi
|
|
|
|
# Assemble the deployable web-bundle/ layout the daemon's `web_static.rs`
|
|
# resolves: the wasm-bindgen `pkg/` output PLUS the vendored CanvasKit artifact
|
|
# under a `canvaskit/` subdir. For the `copy` path the bundle already exists in
|
|
# the build context (./web-bundle); just normalize it into /out/web-bundle so
|
|
# the runtime stage has one stable source path either way.
|
|
RUN mkdir -p /out/web-bundle && \
|
|
if [ "$WEB_BUNDLE_SOURCE" = "build" ]; then \
|
|
cp -R crates/op-host-web/pkg/. /out/web-bundle/ && \
|
|
cp -R crates/op-host-web/assets/canvaskit /out/web-bundle/canvaskit ; \
|
|
else \
|
|
cp -R web-bundle/. /out/web-bundle/ ; \
|
|
fi && \
|
|
echo "assembled web-bundle:" && find /out/web-bundle -maxdepth 2 -type f | sort
|
|
|
|
# ── Stage 2: runtime (slim) ───────────────────────────────────────────────────
|
|
# Only the runtime shared libs the daemon dlopens at run time (GL / fontconfig /
|
|
# freetype + CJK fonts). No Rust toolchain, no node, no build deps.
|
|
FROM debian:bookworm-slim AS runtime
|
|
|
|
ARG SERVE_PORT=3100
|
|
# `local` keeps the historical single-user daemon; `online` adds `--online`,
|
|
# the multi-account mode. Set at run time, so one image serves both.
|
|
ARG SERVE_MODE=local
|
|
ENV OPENPENCIL_SERVE_PORT=${SERVE_PORT} \
|
|
OPENPENCIL_SERVE_MODE=${SERVE_MODE}
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
ca-certificates wget \
|
|
libfreetype6 libfontconfig1 fonts-noto-cjk \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& groupadd -g 10001 openpencil \
|
|
&& useradd -r -u 10001 -g openpencil -M -d /app openpencil
|
|
|
|
WORKDIR /app
|
|
|
|
# Lay out the bundle the way `web_static.rs` expects: the daemon resolves the
|
|
# bundle as `<exe_dir>/web-bundle` (and CanvasKit as `<exe_dir>/web-bundle/
|
|
# canvaskit`). Keep the binary + the bundle dir as siblings under /app so that
|
|
# `<exe_dir> == /app`.
|
|
COPY --from=builder --chown=10001:10001 /src/target/release/op-host-web-server /app/op-host-web-server
|
|
COPY --from=builder --chown=10001:10001 /out/web-bundle /app/web-bundle
|
|
|
|
# The daemon writes nothing outside /tmp in online mode (settings persistence
|
|
# is refused there), so it runs unprivileged and the image can be mounted
|
|
# read-only by the orchestrator.
|
|
USER 10001:10001
|
|
|
|
EXPOSE ${SERVE_PORT}
|
|
|
|
# `/` is the one route served without a credential in every mode, so it is the
|
|
# only probe that works for both `local` and `online`.
|
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=6 \
|
|
CMD wget --quiet --output-document=/dev/null \
|
|
"http://127.0.0.1:${OPENPENCIL_SERVE_PORT}/" || exit 1
|
|
|
|
STOPSIGNAL SIGTERM
|
|
|
|
# Bind 0.0.0.0 so the daemon is reachable from outside the container (the LAN /
|
|
# Docker opt-in documented in `parse_serve_web_args`). No TLS — front with a
|
|
# reverse proxy for anything beyond a trusted network. The port is taken from
|
|
# the build-time SERVE_PORT (baked into OPENPENCIL_SERVE_PORT); `sh -c` lets the
|
|
# env var expand at container start.
|
|
CMD ["sh", "-c", "if [ \"${OPENPENCIL_SERVE_MODE}\" = online ]; then set -- --online; else set --; fi; \
|
|
exec /app/op-host-web-server --serve-web \"${OPENPENCIL_SERVE_PORT}\" --host 0.0.0.0 \"$@\""]
|