From 9004ed51f0d398fcaad24137e2824e088316b2e4 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Wed, 24 Jun 2026 05:39:52 +0000 Subject: [PATCH 1/2] trim unused build/debug packages from chromium images Both images carried apt packages that nothing at runtime uses: - build-essential and a set of -dev headers (libssl-dev, zlib1g-dev, libbz2-dev, libreadline-dev, libsqlite3-dev, libncursesw5-dev, tk-dev, libxml2-dev, libxmlsec1-dev, libffi-dev, liblzma-dev) were left over from a pyenv build path that no longer exists; no compilation happens in the final stage (esbuild and the npm globals ship prebuilt). - ffprobe (~138MB) is only used by an out-of-image test script; the recording server shells out to ffmpeg only. - headful also dropped python2, sqlite3, imagemagick, scrot, xterm, x11-apps, tint2 (never started by supervisor), net-tools, netcat, gstreamer1.0-omx (no-op on x86), and the ppa:mozillateam add plus software-properties-common (the PPA installed nothing). - typescript global npm install (only esbuild is needed at runtime). gpg is now installed explicitly since it was previously pulled in transitively by software-properties-common and is required by install-proxy.sh. headless 2.02GB -> 1.59GB, headful 2.71GB -> 2.12GB. Both built and smoke-tested (chromium, chromedriver, node, ffmpeg, playwright daemon, neko, Xorg + dummy/neko drivers, envoy, gstreamer vp8/opus). Co-Authored-By: Claude Opus 4.7 --- images/chromium-headful/Dockerfile | 44 +++++------------------ images/chromium-headless/image/Dockerfile | 11 +++--- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile index 96901308..a4ba6e31 100644 --- a/images/chromium-headful/Dockerfile +++ b/images/chromium-headful/Dockerfile @@ -177,42 +177,17 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap apt-get update && \ apt-get -y upgrade && \ apt-get --no-install-recommends -y install \ + gpg \ gpg-agent \ # UI Requirements xvfb \ - xterm \ xdotool \ - scrot \ - imagemagick \ sudo \ mutter \ - # Python/pyenv reqs - build-essential \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libreadline-dev \ - libsqlite3-dev \ - curl \ - git \ - libncursesw5-dev \ - xz-utils \ - tk-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libffi-dev \ - liblzma-dev \ - # Network tools - net-tools \ - netcat \ - # PPA req - software-properties-common && \ # Userland apps - sudo add-apt-repository ppa:mozillateam/ppa && \ - sudo apt-get --no-install-recommends -y install \ - x11-apps \ - tint2 \ + curl \ wget \ + xz-utils \ xdg-utils \ libvulkan1 \ fontconfig \ @@ -254,7 +229,6 @@ RUN apt-get update && \ # install ffmpeg manually since the version available in apt is from the 4.x branch due to #drama. COPY --from=ffmpeg-downloader /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg -COPY --from=ffmpeg-downloader /usr/local/bin/ffprobe /usr/local/bin/ffprobe # runtime ENV USERNAME=root @@ -263,13 +237,13 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap set -eux; \ apt-get update; \ apt-get --no-install-recommends -y install \ - wget ca-certificates python2 supervisor xclip xdotool unclutter \ + wget ca-certificates supervisor xclip xdotool unclutter \ pulseaudio dbus-x11 xserver-xorg-video-dummy \ libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \ x11-xserver-utils \ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ - gstreamer1.0-pulseaudio gstreamer1.0-omx; \ + gstreamer1.0-pulseaudio; \ # # install libxcvt0 (not available in debian:bullseye) ARCH=$(dpkg --print-architecture); \ @@ -291,12 +265,11 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \ chown -R $USERNAME:$USERNAME /home/$USERNAME; -# sqlite3 for debugging the cookies file; runtime libs are chrome-for-testing's listed deb.deps. +# runtime libs are chrome-for-testing's listed deb.deps. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-apt-cache \ --mount=type=cache,target=/var/lib/apt,sharing=locked,id=$CACHEIDPREFIX-apt-lib \ apt-get update -y && \ apt-get --no-install-recommends -y install \ - sqlite3 \ libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 \ libcups2 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0 libgtk-3-0 \ libnspr4 libnss3 libpango-1.0-0 libxcomposite1 libxdamage1 \ @@ -333,8 +306,9 @@ RUN set -eux; \ ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack; \ fi -# Install TypeScript, Playwright, Patchright, esbuild globally -RUN --mount=type=cache,target=/root/.npm,id=$CACHEIDPREFIX-npm npm install -g typescript playwright-core patchright esbuild +# Install Playwright, Patchright, esbuild globally (esbuild is required at +# runtime by playwright-daemon.js for TypeScript transformation) +RUN --mount=type=cache,target=/root/.npm,id=$CACHEIDPREFIX-npm npm install -g playwright-core patchright esbuild # Pre-install openssh-server and websocat for faster SSH setup via kernel CLI. # Only installed, NOT configured: no host keys, no custom config, service not enabled. diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index a27861b6..bb110170 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -143,9 +143,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap apt-get -yqq --no-install-recommends install \ ca-certificates \ curl \ - build-essential \ - libssl-dev \ - git \ + gpg \ gpg-agent \ dbus \ dbus-x11 \ @@ -159,7 +157,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap fonts-noto-cjk \ fonts-noto-color-emoji \ fonts-nanum \ - software-properties-common \ supervisor; \ fc-cache -f @@ -191,7 +188,6 @@ COPY shared/chromium-policies/managed/policy.json /etc/chromium/policies/managed # Install FFmpeg (latest static build) for the recording server COPY --from=ffmpeg-downloader /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg -COPY --from=ffmpeg-downloader /usr/local/bin/ffprobe /usr/local/bin/ffprobe # Remove upower to prevent spurious D-Bus activations and logs RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-apt-cache \ @@ -209,8 +205,9 @@ RUN set -eux; \ ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack; \ fi -# Install TypeScript, Playwright, Patchright, esbuild globally -RUN --mount=type=cache,target=/root/.npm,id=$CACHEIDPREFIX-npm npm install -g typescript playwright-core patchright esbuild +# Install Playwright, Patchright, esbuild globally (esbuild is required at +# runtime by playwright-daemon.js for TypeScript transformation) +RUN --mount=type=cache,target=/root/.npm,id=$CACHEIDPREFIX-npm npm install -g playwright-core patchright esbuild # Pre-install openssh-server and websocat for faster SSH setup via kernel CLI. # Only installed, NOT configured: no host keys, no custom config, service not enabled. From 8a3ae4d201d883807f42d20052f63f84c6d66b92 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:17:46 +0000 Subject: [PATCH 2/2] Probe recording durations with ffmpeg instead of ffprobe The audio e2e test shelled into the image to run ffprobe, but ffprobe is no longer shipped. Parse the container and audio durations from ffmpeg output, matching the audio-peak helper that already runs ffmpeg the same way. Co-Authored-By: Claude Opus 4.7 --- server/e2e/e2e_recording_audio_test.go | 60 +++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/server/e2e/e2e_recording_audio_test.go b/server/e2e/e2e_recording_audio_test.go index 74651437..c3152528 100644 --- a/server/e2e/e2e_recording_audio_test.go +++ b/server/e2e/e2e_recording_audio_test.go @@ -449,41 +449,41 @@ func mp4Durations(t *testing.T, data []byte) (float64, float64) { recordingPath := filepath.Join(t.TempDir(), "recording.mp4") require.NoError(t, os.WriteFile(recordingPath, data, 0o644), "failed to write recording for duration analysis") + // Decode the first audio stream to null; ffmpeg reports the container + // duration from the input header and the decoded audio length as the final + // time= progress value. ffprobe is intentionally not shipped in the image. out, err := exec.Command( "docker", "run", "--rm", "-v", recordingPath+":/tmp/recording.mp4:ro", - "--entrypoint", "ffprobe", + "--entrypoint", "ffmpeg", headfulImage, - "-v", "error", - "-show_entries", "format=duration", - "-show_entries", "stream=codec_type,duration", - "-of", "json", - "/tmp/recording.mp4", + "-hide_banner", + "-i", "/tmp/recording.mp4", + "-map", "0:a:0", + "-f", "null", + "-", ).CombinedOutput() - require.NoError(t, err, "failed to probe recording durations: %s", string(out)) - - var probe struct { - Streams []struct { - CodecType string `json:"codec_type"` - Duration string `json:"duration"` - } `json:"streams"` - Format struct { - Duration string `json:"duration"` - } `json:"format"` - } - require.NoError(t, json.Unmarshal(out, &probe), "failed to parse ffprobe output") + require.NoError(t, err, "failed to analyze recording durations: %s", string(out)) - formatDuration, err := strconv.ParseFloat(probe.Format.Duration, 64) - require.NoError(t, err, "failed to parse format duration") + formatMatch := regexp.MustCompile(`Duration: (\d+):(\d+):(\d+(?:\.\d+)?)`).FindStringSubmatch(string(out)) + require.Len(t, formatMatch, 4, "failed to find container duration in ffmpeg output: %s", string(out)) + formatDuration := hmsToSeconds(t, formatMatch[1], formatMatch[2], formatMatch[3]) - for _, stream := range probe.Streams { - if stream.CodecType != "audio" { - continue - } - audioDuration, err := strconv.ParseFloat(stream.Duration, 64) - require.NoError(t, err, "failed to parse audio duration") - return formatDuration, audioDuration - } - t.Fatal("ffprobe did not report an audio stream") - return 0, 0 + timeMatches := regexp.MustCompile(`time=(\d+):(\d+):(\d+(?:\.\d+)?)`).FindAllStringSubmatch(string(out), -1) + require.NotEmpty(t, timeMatches, "failed to find audio duration in ffmpeg output: %s", string(out)) + last := timeMatches[len(timeMatches)-1] + audioDuration := hmsToSeconds(t, last[1], last[2], last[3]) + + return formatDuration, audioDuration +} + +func hmsToSeconds(t *testing.T, h, m, s string) float64 { + t.Helper() + hours, err := strconv.ParseFloat(h, 64) + require.NoError(t, err, "failed to parse hours") + minutes, err := strconv.ParseFloat(m, 64) + require.NoError(t, err, "failed to parse minutes") + seconds, err := strconv.ParseFloat(s, 64) + require.NoError(t, err, "failed to parse seconds") + return hours*3600 + minutes*60 + seconds }