fix(docker): synchronize dev dependency volume

This commit is contained in:
Pi Web Agent
2026-07-13 12:15:18 +00:00
parent d72b14f40a
commit 8b0452d545
6 changed files with 195 additions and 11 deletions
+20 -2
View File
@@ -36,12 +36,22 @@ WORKDIR /workspace
COPY package.json package-lock.json ./
COPY scripts/install-git-hooks.mjs scripts/install-git-hooks.mjs
# Keep an immutable dependency seed outside /workspace, which is hidden by the
# checkout bind mount at runtime. A cached generation is added after custom
# image hooks so it identifies the final dependency tree.
RUN npm ci \
&& ln -sf /workspace/node_modules/.bin/pi /usr/local/bin/pi \
&& install -d -m 0755 /opt/pi-web-dev-dependencies \
&& cp package.json package-lock.json /opt/pi-web-dev-dependencies/ \
&& chmod -R a+rwX /workspace/node_modules \
&& mv /workspace/node_modules /opt/pi-web-dev-dependencies/node_modules \
&& ln -s /opt/pi-web-dev-dependencies/node_modules /workspace/node_modules \
&& ln -sf /opt/pi-web-dev-dependencies/node_modules/.bin/pi /usr/local/bin/pi \
&& npm cache clean --force \
&& chmod -R a+rwX /workspace/node_modules /data \
&& chmod -R a+rwX /data \
&& chmod 0777 /workspace
COPY --chmod=0755 docker/internal/dev/sync-node-modules /usr/local/sbin/pi-web-dev-sync-node-modules
COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker
COPY --from=docker-cli /usr/local/libexec/docker/cli-plugins /usr/local/libexec/docker/cli-plugins
COPY docker/internal/bin/hostexec /usr/local/bin/hostexec
@@ -49,6 +59,8 @@ COPY docker/pi-web-docker /usr/local/bin/pi-web-docker
RUN chmod 0755 /usr/local/bin/hostexec /usr/local/bin/pi-web-docker
COPY docker/custom-image.d/ /tmp/pi-web-custom-image.d/
# Image hooks use the temporary /workspace/node_modules symlink. Leave an empty
# directory afterward so Compose can mount and populate the dependency volume.
RUN bash -euxo pipefail -c '\
shopt -s nullglob; \
for script in /tmp/pi-web-custom-image.d/*.sh; do \
@@ -56,9 +68,15 @@ RUN bash -euxo pipefail -c '\
bash "${script}"; \
done; \
rm -rf /tmp/pi-web-custom-image.d; \
test -L /workspace/node_modules; \
rm /workspace/node_modules; \
install -d -m 0777 /workspace/node_modules; \
zypper clean --all; \
rm -rf /var/cache/zypp/* \
'
# Cache the generation with the completed seed. Changes to any preceding layer,
# including custom image hooks, rerun this step and refresh the named volume.
RUN node -e 'process.stdout.write(`${require("node:crypto").randomUUID()}\n`)' > /opt/pi-web-dev-dependencies/generation
EXPOSE 8504 8505
+2 -6
View File
@@ -327,13 +327,9 @@ Use this shared directory to switch between runtime and dev mode, not to run bot
For sessions to appear under the same workspace in both modes, use the same project path in PI WEB. On Linux, prefer host-mounted paths such as `/home/core/<repo>`, `/srv/<project>`, or `/opt/<project>`. On Mac, prefer paths under `/Users/<you>/...`. The dev container also exposes this checkout as `/workspace` so the PI WEB dev server can run from it, but sessions started against `/workspace` are organized under that different working-directory path and will not line up with runtime sessions for the host-mounted path.
When `package-lock.json` changes, rebuild the dev image and recreate the `node_modules` volume so the bind-mounted checkout sees the new dependency tree:
Development startup keeps the persistent `node_modules` volume synchronized with the dependency tree built into the dev image. When `package.json`, `package-lock.json`, the Node image, or another dependency-build input changes, `start` or `update` rebuilds the image and `data-init` refreshes the volume before `sessiond` starts. Manual volume removal is not required.
```bash
./docker/pi-web-docker --dev stop
docker volume rm pi-web-dev_node_modules
./docker/pi-web-docker --dev start
```
If Compose is invoked directly without rebuilding after a manifest change, `data-init` stops with a mismatch message instead of starting against stale dependencies. Run `./docker/pi-web-docker --dev start` or `./docker/pi-web-docker --dev update` to rebuild and synchronize it.
## Local checkout validation
+2 -2
View File
@@ -56,14 +56,14 @@ services:
set -euo pipefail
mkdir -p /data/home /data/config /data/npm-cache /data/pi-web /data/pi-agent
chown -R "${PI_WEB_UID:-1000}:${PI_WEB_GID:-1000}" /data
/usr/local/sbin/pi-web-dev-sync-node-modules
user: "0:0"
security_opt:
- label=disable
environment:
PI_WEB_UID: ${PI_WEB_UID:-1000}
PI_WEB_GID: ${PI_WEB_GID:-1000}
volumes:
- *pi-web-dev-data-volume
volumes: *pi-web-dev-volumes
sessiond:
build: *pi-web-dev-build
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
log() {
printf '%s\n' "$*" >&2
}
die() {
log "pi-web Docker dev dependencies: $*"
exit 1
}
workspace_dir=${PI_WEB_DEV_WORKSPACE_DIR:-/workspace}
seed_dir=${PI_WEB_DEV_DEPENDENCY_SEED_DIR:-/opt/pi-web-dev-dependencies}
target_dir=$workspace_dir/node_modules
generation_file=$seed_dir/generation
marker_file=$target_dir/.pi-web-dev-dependency-generation
# A direct Compose invocation may skip the image rebuild. Fail closed rather
# than copying dependencies for different checkout manifests.
for manifest in package.json package-lock.json; do
source_manifest=$workspace_dir/$manifest
image_manifest=$seed_dir/$manifest
[ -f "$source_manifest" ] || die "checkout is missing $source_manifest"
[ -f "$image_manifest" ] || die "development image is missing $image_manifest"
if ! cmp -s "$source_manifest" "$image_manifest"; then
die "development image dependencies do not match the checkout; run ./docker/pi-web-docker --dev start or update to rebuild the image"
fi
done
[ -d "$seed_dir/node_modules" ] || die "development image is missing the dependency seed at $seed_dir/node_modules"
[ -s "$generation_file" ] || die "development image is missing its dependency generation at $generation_file"
[ ! -L "$target_dir" ] || die "refusing to synchronize through the node_modules symlink at $target_dir"
mkdir -p "$target_dir"
expected_generation=$(cat "$generation_file")
current_generation=
if [ -f "$marker_file" ]; then
current_generation=$(cat "$marker_file")
fi
if [ "$current_generation" = "$expected_generation" ]; then
log "PI WEB Docker dev dependencies are current."
exit 0
fi
log "Synchronizing PI WEB Docker dev dependencies from the rebuilt image ..."
# Write the marker only after a complete copy so a failed init retries next time.
find "$target_dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
cp -a "$seed_dir/node_modules/." "$target_dir/"
printf '%s\n' "$expected_generation" >"$marker_file"
chmod 0666 "$marker_file"
log "PI WEB Docker dev dependencies synchronized."