@@ -5,12 +5,28 @@ ARG UBUNTU_IMAGE=docker.io/ubuntu:22.04@sha256:962f6cadeae0ea6284001009daa4cc9a8
55# C++ persona patches, distributed as an OCI image. It fully replaces stock
66# chromium (the xtradeb chromium debs are not installed); the chromium
77# runtime-library closure it links against is supplied by the pinned apt
8- # packages in locks/apt-packages.txt. Pinned by digest to the STABLE channel —
9- # tag "149" (Chromium 149.0.7827.232), which blends in with the mass of real
10- # Chrome users, NOT the newer :151 "latest" build. **amd64-only** (no arm64
11- # manifest), so this whole image is amd64-only: build on linux/amd64. Chromium
12- # SIGTRAPs under QEMU, so it must build & run on a native amd64 host.
13- ARG FORTRESS_IMAGE=docker.io/tilion/fortress@sha256:a5e31e67b53c11f5992fb74e4acc4c6c5313cc960def5c5898d198713a58b72a
8+ # packages in locks/apt-packages.txt.
9+ #
10+ # Pinned by digest to the STABLE channel (150). **amd64-only** — Fortress
11+ # publishes no arm64 manifest, so this whole image is amd64-only: build and run
12+ # it on a native amd64 host, since Chromium SIGTRAPs under QEMU emulation.
13+ # To confirm the version in a built image read /json/version over CDP; do NOT
14+ # trust /opt/*/README.txt, whose banner carries a stale generic string.
15+ #
16+ # 150 is built from CURRENT STABLE Chrome, which matters on its own: version-database
17+ # detectors read an older build as out-of-date, and a version that does not publicly
18+ # exist as generic "Chromium". This release also carries the fixes from upstream's
19+ # cycle (tiliondev/fortress#25 and the 150 notes):
20+ # - per-clone canvas/audio divergence — the noise seed is applied correctly, so two
21+ # instances no longer share one un-noised fingerprint
22+ # - explicit --uxr-* flags now WIN over the generated persona (previously --uxr-ua-os /
23+ # --uxr-ua-platform were overwritten, so a UA override silently stayed Windows and
24+ # the resulting UA-vs-platform mismatch read as tampering)
25+ # - the launcher honours the documented TILION_* env names, not only TILLION_*
26+ # - geo-matched persona: timezone/locale derived from the egress IP, since a timezone
27+ # disagreeing with the IP reads as VPN
28+ # - coherent OS voices, and the completed WebGL parameter spoof under software render
29+ ARG FORTRESS_IMAGE=docker.io/tilion/fortress@sha256:b4f331693d3db25d70d814a99ed0da6be433d884e18d5af1cf3e6f3a2969569b
1430
1531FROM ${GOLANG_IMAGE} AS proxy-builder
1632WORKDIR /src
@@ -104,7 +120,9 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
104120 novnc_url="$(awk -F '=' '$1 == " url" { print $2 }' /tmp/novnc.lock)" ; \
105121 novnc_sha256="$(awk -F '=' '$1 == " sha256" { print $2 }' /tmp/novnc.lock)" ; \
106122 mkdir -p /tmp/novnc-src /usr/share/novnc; \
107- curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --output /tmp/novnc.tar.gz "$novnc_url" ; \
123+ curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 \
124+ --connect-timeout 15 --speed-limit 4096 --speed-time 20 --max-time 300 \
125+ --ipv4 --output /tmp/novnc.tar.gz "$novnc_url" ; \
108126 echo "${novnc_sha256} /tmp/novnc.tar.gz" | sha256sum -c -; \
109127 tar -xzf /tmp/novnc.tar.gz --strip-components=1 -C /tmp/novnc-src; \
110128 cp -a /tmp/novnc-src/core /tmp/novnc-src/vendor /usr/share/novnc/; \
@@ -144,17 +162,24 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
144162 -newerct "$touch_start" \
145163 -exec touch -h -d "@${SOURCE_DATE_EPOCH}" {} +
146164
147- # Tilion Fortress (stable stealth Chromium 149 ) — the only browser in the image.
165+ # Tillion Fortress (stable stealth Chromium 150 ) — the only browser in the image.
148166# /opt/tilion/tilion is a bash launcher that applies a Windows persona + bundled
149167# fonts + its own SwiftShader ICD around /opt/tilion/chrome (the patched Chromium
150168# whose C++-level patches produce a real-Chrome TLS/JS fingerprint). The shared
151169# libs it links against come from the chromium runtime-lib block in
152170# locks/apt-packages.txt. `chromium`/`chromium-browser` are symlinked to the
153171# launcher so anything invoking `chromium` gets the persona-wrapped browser.
154172# Own layer so the ~326MB copy caches independently.
155- COPY --from=fortress /opt/tilion /opt/tilion
173+ # Copy /opt wholesale rather than a fixed subdirectory: the 151 image renamed
174+ # /opt/tilion/tilion to /opt/tillion/tillion (double L). Normalising to a stable
175+ # /opt/tilion/tilion below means the rest of the image — the symlinks, start-chromium's
176+ # CHROME_BIN, the fonts template patch — is unaffected by upstream renaming, and the
177+ # build fails loudly rather than silently launching nothing if the layout changes again.
178+ COPY --from=fortress /opt /opt
156179RUN set -eux; \
157180 touch_start="$(date -u +'%Y-%m-%d %H:%M:%S')" ; \
181+ if [ -d /opt/tillion ] && [ ! -e /opt/tilion ]; then ln -s tillion /opt/tilion; fi; \
182+ if [ -x /opt/tilion/tillion ] && [ ! -e /opt/tilion/tilion ]; then ln -s tillion /opt/tilion/tilion; fi; \
158183 test -x /opt/tilion/tilion; test -x /opt/tilion/chrome; \
159184 ln -sf /opt/tilion/tilion /usr/bin/chromium; \
160185 ln -sf /opt/tilion/tilion /usr/bin/chromium-browser; \
@@ -217,6 +242,49 @@ COPY --from=proxy-builder /out/novnc-proxy /usr/local/bin/novnc-proxy
217242COPY --chmod=0755 entrypoint.sh /usr/local/bin/minimal-vnc-entrypoint
218243COPY --chmod=0755 start-chromium /usr/local/bin/start-chromium
219244COPY liveview.html /usr/share/novnc/liveview.html
245+ COPY kbd-autofocus.js /usr/share/novnc/kbd-autofocus.js
246+ COPY kbd /usr/share/novnc/kbd
247+ COPY viewer.js /usr/share/novnc/viewer.js
248+ COPY locks/esbuild.lock /tmp/esbuild.lock
249+
250+ # Bundle the viewer (noVNC core/* + the kbd/* IME layer + viewer.js) into a single
251+ # viewer.bundle.js so the page loads in ONE request instead of a ~70-module ES
252+ # graph that the browser discovers level-by-level over the tunnel (measured
253+ # multi-second cold start on 3G). esbuild is a standalone Go binary — no Node —
254+ # pinned by sha256 exactly like noVNC. --format=esm keeps module semantics.
255+ # --minify-whitespace/--minify-syntax strip whitespace + fold dead code (~-22% on
256+ # the wire after gzip) WITHOUT mangling identifiers, so function names stay in
257+ # stack traces and the BUILD_TAG string stays greppable — the bundle stays
258+ # debuggable. (Full --minify would mangle names; deliberately not used.) esbuild's
259+ # output is deterministic (no embedded timestamps), preserving reproducibility.
260+ RUN set -eux; \
261+ esb_url="$(awk -F '=' '$1 == " url" { print $2 }' /tmp/esbuild.lock)" ; \
262+ esb_sha256="$(awk -F '=' '$1 == " sha256" { print $2 }' /tmp/esbuild.lock)" ; \
263+ curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 \
264+ --connect-timeout 15 --speed-limit 4096 --speed-time 20 --max-time 300 \
265+ --ipv4 --output /tmp/esbuild.tgz "$esb_url" ; \
266+ echo "${esb_sha256} /tmp/esbuild.tgz" | sha256sum -c -; \
267+ mkdir -p /tmp/esbuild-src; \
268+ tar -xzf /tmp/esbuild.tgz --strip-components=1 -C /tmp/esbuild-src package/bin/esbuild; \
269+ /tmp/esbuild-src/bin/esbuild /usr/share/novnc/viewer.js --bundle --format=esm \
270+ --minify-whitespace --minify-syntax \
271+ --outfile=/usr/share/novnc/viewer.bundle.js; \
272+ rm -rf /tmp/esbuild.tgz /tmp/esbuild-src /tmp/esbuild.lock; \
273+ # Content-hash the bundle so novnc-proxy can serve it immutably (max-age=1y):
274+ # a content change yields a NEW url, so a reconnect/reopen skips re-downloading
275+ # it entirely — with zero staleness risk (the reason liveview.html itself stays
276+ # no-store, pointing at whatever hash is current). Deterministic: the hash is of
277+ # esbuild's deterministic output. Rewrite the <script src> to the hashed name.
278+ bundle_hash="$(sha256sum /usr/share/novnc/viewer.bundle.js | cut -c1-12)" ; \
279+ mv /usr/share/novnc/viewer.bundle.js "/usr/share/novnc/viewer-${bundle_hash}.bundle.js" ; \
280+ sed -i "s#src=\" viewer\. bundle\. js\" #src=\" viewer-${bundle_hash}.bundle.js\" #" \
281+ /usr/share/novnc/liveview.html; \
282+ # Precompress the (hashed) bundle: it's the cold-start bottleneck over the
283+ # tunnel and novnc-proxy serves the .gz via Accept-Encoding negotiation. -n
284+ # drops the name/mtime from the gzip header so the output is deterministic;
285+ # -k keeps the raw file for clients without gzip.
286+ gzip -9 -n -k -f "/usr/share/novnc/viewer-${bundle_hash}.bundle.js"
287+
220288COPY third-party/fortress /usr/share/doc/popcorn/third-party/fortress
221289COPY --chown=kernel:kernel extensions/proxy /home/kernel/extensions/proxy
222290COPY --chown=kernel:kernel policies/managed/policy.json /etc/chromium/policies/managed/policy.json
@@ -228,8 +296,12 @@ RUN set -eux; \
228296 /usr/local/bin/novnc-proxy \
229297 /usr/local/bin/minimal-vnc-entrypoint \
230298 /usr/local/bin/start-chromium \
231- /usr/share/novnc/liveview.html; \
232- find /home/kernel/extensions /etc/chromium/policies /etc/chromium/policy-variants /usr/share/doc/popcorn -exec touch -h -d "@${SOURCE_DATE_EPOCH}" {} +
299+ /usr/share/novnc/liveview.html \
300+ /usr/share/novnc/kbd-autofocus.js \
301+ /usr/share/novnc/viewer.js \
302+ /usr/share/novnc/viewer-*.bundle.js \
303+ /usr/share/novnc/viewer-*.bundle.js.gz; \
304+ find /usr/share/novnc/kbd /home/kernel/extensions /etc/chromium/policies /etc/chromium/policy-variants /usr/share/doc/popcorn -exec touch -h -d "@${SOURCE_DATE_EPOCH}" {} +
233305
234306USER kernel
235307EXPOSE 6080 9222 9226
0 commit comments