-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.claude
More file actions
258 lines (243 loc) · 14 KB
/
Copy pathDockerfile.claude
File metadata and controls
258 lines (243 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# Extends the base claude-flow image with the Scribus template pipeline toolchain.
#
# Base provides: node 20, python3, git/git-lfs, gh, jq, ripgrep, uv, ruff, mypy, pytest.
# This image adds:
# - Scribus 1.6.x (from Debian trixie, since bookworm only ships 1.5.8 which
# cannot read the project's 1.6.5 .sla files). Available for amd64 and arm64.
# - Xvfb (Scribus needs an X display even in --no-gui mode on Linux)
# - Poppler-utils (pdftoppm / pdfinfo / pdftocairo for rasterising PDFs)
# - Ghostscript (PDF post-processing, PDF/X conversion)
# - ImageMagick (compare, convert — visual diff fallback)
# - libxml2-utils (xmllint for validating SLA XML in CI)
# - python3-lxml, python3-yaml (used by tools/sla_lib and tools/render.py).
# Pulled from trixie too: trixie scribus depends on libpython3.13, which
# promotes the system python from 3.11 → 3.13. Bookworm's python3-lxml /
# python3-yaml are pinned to python<3.12 and can no longer be installed
# once that promotion happens, so they must come from trixie as well.
#
# Note: CI does NOT use this image. The GitHub Pages workflow installs Scribus
# from the upstream linux-x86_64 AppImage on the Ubuntu runner (faster, no
# Debian release crossing). This Dockerfile is the local container path on
# Mac/arm64 where no official Scribus AppImage exists.
#
# Optional preflight/diff tools (veraPDF, pdfcpu, odiff) are NOT installed by
# default to keep the image small. Enable with:
# docker build --build-arg INSTALL_PREFLIGHT=1 -f Dockerfile.claude -t scribus-pipeline .
#
# Build (default):
# docker build -f Dockerfile.claude -t scribus-pipeline .
# Build (with preflight + visual diff stack):
# docker build --build-arg INSTALL_PREFLIGHT=1 -f Dockerfile.claude -t scribus-pipeline .
# Run an ad-hoc render:
# docker run --rm -v "$PWD":/root/workspace scribus-pipeline \
# python3 tools/render.py templates/postkarte-a6-kampagne --sample klimaschutz
FROM ghcr.io/flomotlik/claude-code:latest
ARG INSTALL_PREFLIGHT=0
ARG VERAPDF_VERSION=1.26.2
ARG PDFCPU_VERSION=0.8.0
# Pin Scribus to Debian trixie because bookworm has only 1.5.8.
# We pin only the scribus packages from trixie to avoid pulling in unrelated upgrades.
RUN echo "deb http://deb.debian.org/debian trixie main" \
> /etc/apt/sources.list.d/trixie-scribus.list && \
printf 'Package: *\nPin: release n=trixie\nPin-Priority: 100\n\nPackage: scribus scribus-data scribus-doc\nPin: release n=trixie\nPin-Priority: 990\n' \
> /etc/apt/preferences.d/99-trixie-scribus && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
-t trixie \
scribus scribus-data \
python3-lxml python3-yaml && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
xvfb \
xauth \
poppler-utils \
ghostscript \
imagemagick \
libxml2-utils \
fonts-dejavu-core \
libzbar0 \
zbar-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Python tooling deps for QR + library demo-content authoring (issues #11, #13):
# - qrcode[pil]==8.2: deterministic branded QR rendering (Dunkelgrün modules,
# center-logo embed, ECC=H). Used by tools/qr_gen.py.
# - pyzbar==0.1.9: QR decode for scannability tests. Needs libzbar0 at runtime
# (installed above) and zbar-tools provides zbarimg for shell-level smoke.
# - Pillow pinned to 12.2.0 for byte-deterministic library regen (issue #13 —
# see RESEARCH.md R-DOCKER-PIN). The trixie scribus stack ships 12.2.0
# transitively; the explicit pin guards against transitive drift on rebuild
# so library.crop_for_frame() and add_demo_watermark() produce byte-identical
# output across container regenerations.
# - jsonschema==4.26.0: validates shared/sample-images/manifest.yml against the
# library Draft 2020-12 schema (issue #13).
# - SimpleIDML==1.3.1: read-only IDML zip + XML access for tools/idml_to_dsl.py
# (issue #35). Dev-only: only used by the one-shot IDML→DSL bootstrap; not
# touched by CI render path. Pinning here because the dev container is the
# same image that runs the converter. Never import simple_idml.indesign
# (pulls in LGPL suds-py3 SOAP stack); we only use simple_idml.idml (BSD-3).
RUN pip3 install --break-system-packages --no-cache-dir \
'qrcode[pil]==8.2' \
'pyzbar==0.1.9' \
'pillow==12.2.0' \
'jsonschema==4.26.0' \
'SimpleIDML==1.3.1' \
'pdfplumber==0.11.9'
# Proprietary drop zone (Gotham Narrow). Source files live at
# /root/workspace/fonts/ (gitignored; user-controlled drop zone) and are NOT
# part of the public repo. Gotham is no longer referenced by any template (all
# replaced by Barlow), so the drop zone is retained only so the maintainer's
# legacy proprietary faces remain available; the build no longer hard-fails on
# their absence.
#
# IMPORTANT: this block must NOT install Vollkorn. The committed fonts/vollkorn/
# block below is the single source of truth for the two accent italics. A host
# drop zone may carry a competing Vollkorn variable font
# (Vollkorn-Italic-VariableFont_wght.ttf) whose filename differs from the
# committed statics, so it would survive (not be overwritten) and compete in
# fontconfig — letting Scribus resolve "Vollkorn * Italic" to the variable
# instance instead of the vendored, license-audited static. So skip any
# Vollkorn-* file here and let the committed block install them exclusively.
#
# See docs/render-fidelity.md for the full font-pipeline documentation.
COPY fonts/ /tmp/fonts-staging/
RUN set -e && \
mkdir -p /usr/local/share/fonts/gruene && \
find /tmp/fonts-staging -type f \( -iname '*.otf' -o -iname '*.ttf' \) \
! -iname 'Vollkorn-*' \
-exec install -m 644 {} /usr/local/share/fonts/gruene/ \; && \
fc-cache -f && \
N=$(fc-list | grep -ciE 'gotham narrow' || true) && \
echo "Brand drop-zone fonts installed: $N Gotham faces (Vollkorn-* skipped — owned by fonts/vollkorn/)" && \
rm -rf /tmp/fonts-staging
# Barlow Semi Condensed (SIL-OFL) — the single print font for all templates.
# These TTFs ARE committed to the repo (fonts/barlow-semi-condensed/, a tracked
# .gitignore exception) because Scribus renders locally and cannot pull a
# webfont. Deliberate print-pipeline vendoring exception, same justification as
# the Vollkorn vendoring above. No fontconfig alias is needed: Scribus reads the
# TTF name table, so "Barlow Semi Condensed Regular/Bold/Black/ExtraBold" all
# resolve natively. The sanity-check tests the bare family + a face count, NOT
# the per-weight strings (fc-match resolves "… Regular"/"… Bold" to DejaVu by
# design, so a per-weight check would spuriously fail).
COPY fonts/barlow-semi-condensed/ /tmp/barlow-staging/
RUN install -m 644 /tmp/barlow-staging/*.ttf /usr/local/share/fonts/gruene/ && \
fc-cache -f && \
BN=$(fc-list | grep -ci 'barlow semi condensed' || true) && \
if [ "$BN" -lt 4 ]; then \
echo "BARLOW INSTALL FAILED: $BN faces (expected >= 4)" >&2; exit 1; \
fi && \
fc-match "Barlow Semi Condensed" | grep -qi barlow || \
{ echo "fc-match did not resolve to Barlow" >&2; exit 1; } && \
echo "Barlow installed: $BN faces" && rm -rf /tmp/barlow-staging
# Raleway (SIL-OFL) — the print font for the "Raleway -3" brand set. Committed
# under fonts/raleway/ (tracked .gitignore exception); Scribus renders locally
# and cannot pull a webfont. Static per-weight TTFs (Regular/Bold/ExtraBold/
# Black); Scribus reads the name table so "Raleway Regular/Bold/Black" resolve
# natively. Sanity-check on the bare family + face count (fc-match resolves
# "Raleway Regular" to DejaVu by design — a per-weight check would misfire).
COPY fonts/raleway/ /tmp/raleway-staging/
RUN install -m 644 /tmp/raleway-staging/*.ttf /usr/local/share/fonts/gruene/ && \
fc-cache -f && \
RN=$(fc-list | grep -ci 'raleway' || true) && \
if [ "$RN" -lt 4 ]; then \
echo "RALEWAY INSTALL FAILED: $RN faces (expected >= 4)" >&2; exit 1; \
fi && \
fc-match "Raleway" | grep -qi raleway || \
{ echo "fc-match did not resolve to Raleway" >&2; exit 1; } && \
echo "Raleway installed: $RN faces" && rm -rf /tmp/raleway-staging
# Vollkorn accent italics (SIL-OFL, Friedrich Althausen) — the two faces the
# templates use: "Vollkorn Black Italic" (headline accents) and "Vollkorn Bold
# Italic" (the zweigeteilt pull-quote). The genuine static TTFs ARE committed to
# the repo (fonts/vollkorn/, a tracked .gitignore exception) so the build is
# reproducible WITHOUT the gitignored host drop zone above. Scribus renders
# locally and cannot pull a webfont; this is the same deliberate print-pipeline
# vendoring exception as Barlow. We install the committed files explicitly here
# (overwriting any same-named copy that the proprietary drop zone may have
# dropped) so the rendered output is pinned to the vendored, license-audited
# files — not whatever happens to be on the build host.
COPY fonts/vollkorn/ /tmp/vollkorn-staging/
RUN install -m 644 /tmp/vollkorn-staging/*.ttf /usr/local/share/fonts/gruene/ && \
fc-cache -f && \
VN=$(fc-list | grep -ci 'vollkorn' || true) && \
if [ "$VN" -lt 2 ]; then \
echo "VOLLKORN INSTALL FAILED: $VN faces (expected >= 2)" >&2; exit 1; \
fi && \
echo "Vollkorn committed italics installed: $VN faces" && rm -rf /tmp/vollkorn-staging
# Fontconfig family-name aliases for the Vollkorn accent italics. Scribus SLAs
# reference these fonts by their full name as a family ("Vollkorn Black Italic" /
# "Vollkorn Bold Italic"); the static files register as family=Vollkorn +
# style="Black Italic" / "Bold Italic". Without these aliases, fontconfig cannot
# match the lookup and Scribus falls back to DejaVu. Vollkorn stays the accent
# font (only Gotham was replaced by Barlow), so both italic faces must resolve.
COPY shared/fonts/50-vollkorn-family-alias.conf /etc/fonts/conf.d/50-vollkorn-family-alias.conf
RUN fc-cache -f && \
BI=$(fc-match "Vollkorn Black Italic"); \
OI=$(fc-match "Vollkorn Bold Italic"); \
echo "$BI" | grep -qi 'Vollkorn-BlackItalic' || \
{ echo "fc-match 'Vollkorn Black Italic' did not resolve to genuine Vollkorn-BlackItalic.ttf: $BI" >&2; exit 1; }; \
echo "$OI" | grep -qi 'Vollkorn-BoldItalic' || \
{ echo "fc-match 'Vollkorn Bold Italic' did not resolve to genuine Vollkorn-BoldItalic.ttf: $OI" >&2; exit 1; }; \
echo "$BI$OI" | grep -qi 'DejaVu' && \
{ echo "Vollkorn italic resolved to DejaVu fallback — install/alias broken: $BI / $OI" >&2; exit 1; }; \
[ "$BI" != "$OI" ] || \
{ echo "Vollkorn Black Italic and Bold Italic resolved to the SAME file — weights collapsed: $BI" >&2; exit 1; }; \
echo "Vollkorn aliases OK (genuine, distinct): $BI; $OI"
# Allow ImageMagick to read/write PDF (Debian's policy.xml blocks PDF by default).
# We need this for compare/convert in visual diff workflows.
RUN if [ -f /etc/ImageMagick-6/policy.xml ]; then \
sed -i 's|<policy domain="coder" rights="none" pattern="PDF" />|<policy domain="coder" rights="read\|write" pattern="PDF" />|' \
/etc/ImageMagick-6/policy.xml; \
fi
# Optional: preflight + visual-diff stack
# - veraPDF: PDF/X / PDF/A conformance checker
# - pdfcpu: PDF integrity validator (Go binary, multi-arch)
# - default-jre: required by veraPDF
RUN if [ "$INSTALL_PREFLIGHT" = "1" ]; then \
set -e; \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
default-jre-headless \
unzip && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) pdfcpu_arch=amd64 ;; \
arm64) pdfcpu_arch=arm64 ;; \
*) echo "Unsupported architecture for pdfcpu: $arch" >&2; exit 1 ;; \
esac; \
curl -fsSL "https://github.com/pdfcpu/pdfcpu/releases/download/v${PDFCPU_VERSION}/pdfcpu_${PDFCPU_VERSION}_Linux_${pdfcpu_arch}.tar.xz" \
| tar -xJ -C /usr/local/bin --strip-components=1 --wildcards '*/pdfcpu' && \
chmod +x /usr/local/bin/pdfcpu && \
mkdir -p /opt/verapdf && \
curl -fsSL -o /tmp/verapdf.zip \
"https://software.verapdf.org/releases/${VERAPDF_VERSION}/verapdf-greenfield-${VERAPDF_VERSION}-installer.zip" && \
unzip -q /tmp/verapdf.zip -d /tmp/verapdf-installer && \
installer_dir="$(find /tmp/verapdf-installer -maxdepth 1 -type d -name 'verapdf*' | head -1)"; \
printf 'INSTALL_PATH=/opt/verapdf\nMODE=cli\n' > /tmp/verapdf-auto.xml && \
"$installer_dir"/verapdf-install /tmp/verapdf-auto.xml || true; \
ln -sf /opt/verapdf/verapdf /usr/local/bin/verapdf 2>/dev/null || true; \
rm -rf /tmp/verapdf.zip /tmp/verapdf-installer /tmp/verapdf-auto.xml; \
fi
# odiff (visual-diff): pure binary, no JS runtime needed
RUN if [ "$INSTALL_PREFLIGHT" = "1" ]; then \
arch="$(uname -m)"; \
case "$arch" in \
x86_64) odiff_arch=linux-x64 ;; \
aarch64) odiff_arch=linux-arm64 ;; \
*) echo "Unsupported architecture for odiff: $arch" >&2; exit 1 ;; \
esac; \
npm install -g "odiff-bin@^3" || true; \
fi
# Sanity probe: confirm the binaries / python deps are present and importable
# so the layer-cached image catches install regressions at build time.
# Note: `scribus --version` prints "Scribus X.Y.Z" (not a bare version string)
# and may exit non-zero under xvfb if Qt fails to fully init — we tolerate that
# and only require the binary to be on PATH and produce some output.
RUN set -e; \
command -v scribus >/dev/null && echo "scribus: $(command -v scribus)"; \
xvfb-run -a --server-args="-screen 0 800x600x24" scribus --version 2>&1 | head -5 || true; \
pdftoppm -v 2>&1 | head -1; \
python3 -c "import lxml.etree, yaml, simple_idml.idml; print('python deps ok')"
# Default working directory is the mounted repo (volume from base image).
WORKDIR /root/workspace
# Intentionally NO CMD / ENTRYPOINT override — the base image
# (ghcr.io/flomotlik/claude-code:latest) sets up the entrypoint that launches
# claude inside the container. Overriding it here breaks `claude-flow run`.