openpencil/Dockerfile.web-rust
Kayshen-X 88a571c028 refactor(host): rename op-web-daemon crate to op-host-services
The extracted headless crate is consumed by BOTH op-host-desktop (the GUI binary, for its
embedded --serve-web/MCP/chat/export) AND op-host-web-server — so 'web-daemon' was misleading.
Renamed crate dir + package + lib (op_web_daemon -> op_host_services) across all consumer files
(114 refs in 24 files) + the 4 Cargo.toml deps + Dockerfile/guard comments; identity docs
rewritten (it's the GUI-free host backend — daemon/MCP/AI/export/persistence — not web-specific).
No behavior change. (Lock renamed accordingly; the concurrent actor's op-host-web serde line excluded.)
2026-06-19 22:35:22 +08:00

133 lines
6.7 KiB
Docker

# syntax=docker/dockerfile:1
#
# Dockerfile.web-rust — container for the RUST web host (C1).
#
# Serves the Rust CanvasKit web editor: the `openpencil-desktop` 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 Rust analog of the root `Dockerfile` (which ships the retired
# TS/Nitro web app via `bun run ./out/web/server/index.mjs`).
#
# 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:
# docker run -p 3100:3100 openpencil-web-rust
# # then open http://localhost:3100/
#
# 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 + binaryen (wasm-opt) + node,
# all apt/cargo-installable 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 where binaryen + node +
# gzip are apt-installable, matching the prerequisites the local gate script
# (`tools/check-wasm-bundle.sh`) asserts.
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
# Port baked into the image's CMD; overridable at build + run time.
ARG SERVE_PORT=3100
# 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 \
binaryen \
nodejs \
gzip \
ca-certificates \
pkg-config \
libfreetype-dev libfontconfig1-dev fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
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)" && \
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
ENV OPENPENCIL_SERVE_PORT=${SERVE_PORT}
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libfreetype6 libfontconfig1 fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
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 /src/target/release/op-host-web-server /app/op-host-web-server
COPY --from=builder /out/web-bundle /app/web-bundle
EXPOSE ${SERVE_PORT}
# 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", "exec /app/op-host-web-server --serve-web \"${OPENPENCIL_SERVE_PORT}\" --host 0.0.0.0"]