chore(docker): add Update-Codeman.sh for scripted major-update rebuilds

docker/README.md and docs/docker-self-update.md both already point operators
at "stop the stack, rebuild, restart" for anything the in-app updater refuses
to apply (a changed server.Dockerfile, a changed docker-compose.yaml, or a
new required .env key) — but that was a manual, hand-typed procedure with no
script of its own, unlike every other start/update path this deployment has.

docker/Update-Codeman.sh scripts it: `docker compose down`, then an
unconditional `docker compose build --no-cache` (a major update should be
certain of what actually ships, not reuse whatever layers happened to be
cached), then hands off to the existing Start-Codeman.sh for the same
careful PUID/PGID, override-file and fingerprint handling every other start
already goes through — rather than reimplementing any of that by hand and
risking it drifting out of step.

An optional --volumes/-v flag also removes the codeman-node-modules/
codeman-dist named volumes, the scripted form of the "Resetting the build
artefacts" procedure docs/docker-self-update.md already documents by hand.
Safe: those two are the only named volumes this stack declares; application
data and case workspaces are host bind mounts, never touched by
`docker compose down` either way.

Docs updated: a "Major updates" section in docker/README.md, and a pointer
from docs/docker-self-update.md's existing "Resetting the build artefacts"
troubleshooting entry.

Tests: extended test/docker-entrypoint.test.ts (the existing home for
Start-Codeman.sh's own static checks) with a bash -n parse check, the
down-before-build-before-handoff ordering, the --volumes flag's effect,
unrecognised-argument handling, and byte-for-byte agreement with
Start-Codeman.sh's own override-file resolution logic (so `down` here and
`up` there can never target different Compose files).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
This commit is contained in:
Devvyn
2026-09-21 13:57:54 +08:00
co-authored by Claude Sonnet 5
parent 9466acfc1a
commit 9ba90a674a
4 changed files with 186 additions and 2 deletions
+20
View File
@@ -41,6 +41,26 @@ Releases that change `server.Dockerfile`, `docker-compose.yaml`, or add a key to
changed, and asks you to run `Start-Codeman.sh` here on the host instead. Details:
[`../docs/docker-self-update.md`](../docs/docker-self-update.md).
### Major updates
`Start-Codeman.sh` rebuilds the image and clears the build-artefact volumes on
its own, but only when it detects the checkout's HEAD or `package-lock.json`
moved — exactly right for an ordinary `git pull`, too conservative when a
release note (or the updater's own blocker message) calls for starting over.
For that case, `docker/Update-Codeman.sh` stops the stack, force-rebuilds the
image with no layer cache, then hands off to `Start-Codeman.sh` for the usual
start:
```sh
bash docker/Update-Codeman.sh
```
Add `--volumes` to also clear the `codeman-node-modules`/`codeman-dist`
volumes — the scripted form of "Resetting the build artefacts" in
[`../docs/docker-self-update.md`](../docs/docker-self-update.md). Those two
are the only named volumes this stack declares; application data and case
workspaces are host bind mounts and are never touched either way.
## Local customisation
Compose merges `docker-compose.override.yml` on top of `docker-compose.yaml`. Keep host-specific changes there rather than editing `docker-compose.yaml`, so this repository can be updated without losing them. Both `docker-compose.override.yml` and `docker-compose.override.yaml` are ignored by Git.
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
#
# The scripted major-update path for the Docker Compose deployment.
#
# docker/README.md and docs/docker-self-update.md both point operators here for
# anything the in-app updater itself refuses to apply: a changed
# `server.Dockerfile`, a changed `docker-compose.yaml`, or a new required
# `.env.example` key. None of those can be applied by a container restarting
# itself — a restart reuses the existing image and configuration (see "The
# environment gate" in docs/docker-self-update.md) — so this script does the
# three things an in-place update cannot: stop the stack, force a real image
# rebuild with no layer cache, then hand off to Start-Codeman.sh for the same
# careful PUID/PGID, override-file and fingerprint handling every other start
# goes through.
#
# This is the scripted form of "Resetting the build artefacts" in
# docs/docker-self-update.md (`docker compose down -v`, then
# `Start-Codeman.sh`), plus the unconditional `--no-cache` a major update
# warrants: `Start-Codeman.sh` on its own only rebuilds without the cache flag,
# and only clears the two build-artefact volumes when it detects the checkout's
# HEAD or `package-lock.json` moved — exactly right for an ordinary `git pull`,
# too conservative when the ask is "start over, certain of what ships".
#
# Usage: docker/Update-Codeman.sh [--volumes]
# --volumes, -v Also remove the codeman-node-modules/codeman-dist named
# volumes, so the fresh image's own node_modules/dist are
# what actually run instead of sitting unused behind a
# Docker-seeded volume's old content (Docker only seeds a
# named volume from the image while that volume is EMPTY).
# Safe: those two are the ONLY named volumes this stack
# declares (`docker-compose.yaml`) — application data and
# case workspaces are host bind mounts, never touched by
# `docker compose down`, with or without this flag.
set -euo pipefail
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
env_file="$script_dir/.env"
compose_file="$script_dir/docker-compose.yaml"
remove_volumes=0
for arg in "$@"; do
case "$arg" in
--volumes | -v)
remove_volumes=1
;;
*)
printf 'Error: unrecognised argument: %s\n' "$arg" >&2
printf 'Usage: %s [--volumes]\n' "$0" >&2
exit 1
;;
esac
done
if [[ ! -f "$env_file" ]]; then
printf 'Error: Docker environment file is missing: %s\n' "$env_file" >&2
printf 'Create it from %s/.env.example before running this script.\n' "$script_dir" >&2
exit 1
fi
# Same override-file discovery as Start-Codeman.sh, and deliberately kept in
# step with it: a stack started through one script and updated through the
# other must resolve to the exact same Compose files, or `down` here and `up`
# there could target different configurations. Compose's own precedence
# (measured on v5.5.0 with both present: it uses .yml and ignores .yaml).
override_yml="$script_dir/docker-compose.override.yml"
override_yaml="$script_dir/docker-compose.override.yaml"
if [[ -f "$override_yml" && -f "$override_yaml" ]]; then
printf 'Warning: both %s and %s exist; Compose uses .yml and ignores .yaml.\n' \
"$override_yml" "$override_yaml" >&2
fi
compose_files=(-f "$compose_file")
for override_file in "$override_yml" "$override_yaml"; do
if [[ -f "$override_file" ]]; then
compose_files+=(-f "$override_file")
printf 'Using Compose override file: %s\n' "$override_file"
break
fi
done
compose_command=(docker compose --env-file "$env_file" "${compose_files[@]}")
printf 'Stopping the stack...\n'
if [[ "$remove_volumes" == '1' ]]; then
printf 'Also removing the codeman-node-modules/codeman-dist volumes (--volumes).\n'
"${compose_command[@]}" down --volumes
else
"${compose_command[@]}" down
fi
# --no-cache, always: a plain `build` reuses cached layers (npm install, apt
# packages, the CLI installs baked into the image) and can silently keep them
# frozen at whatever they were the day the cache was populated — exactly wrong
# for a major update, whose whole point is being certain of what actually
# ships. `scripts/build-agent-image.mjs` makes the same call for the same
# reason (see its entry in CLAUDE.md's Additional Commands table).
printf 'Building a fresh image (--no-cache)...\n'
"${compose_command[@]}" build --no-cache
# Start-Codeman.sh does everything a plain `up -d` does not: resolves
# PUID/PGID from CODEMAN_APPDATA_PATH's owner, pre-creates CODEMAN_CASES_PATH
# with the right ownership, resolves DOCKER_SOCKET_GID, records the
# server.Dockerfile/docker-compose.yaml fingerprint the in-app updater's gate
# reads on every future update, and clears the build-artefact volumes itself
# if it finds the checkout's source moved since the last start. Reimplementing
# any of that here would only risk drifting out of step with it — hand off
# instead, exactly as docs/docker-self-update.md's own reset procedure does.
printf 'Handing off to Start-Codeman.sh...\n'
exec "$script_dir/Start-Codeman.sh"