diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile index 74679c87..9a1c9da2 100644 --- a/images/chromium-headful/Dockerfile +++ b/images/chromium-headful/Dockerfile @@ -177,7 +177,6 @@ COPY images/chromium-headful/supervisor/services/ /etc/supervisor/conf.d/service # copy the kernel-images API binary built in the builder stage COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api -ENV WITH_KERNEL_IMAGES_API=false RUN useradd -m -s /bin/bash kernel diff --git a/images/chromium-headful/run-docker.sh b/images/chromium-headful/run-docker.sh index 5adbd8a8..2a9f4409 100755 --- a/images/chromium-headful/run-docker.sh +++ b/images/chromium-headful/run-docker.sh @@ -34,19 +34,15 @@ RUN_ARGS=( --tmpfs /dev/shm:size=2g -v "$HOST_RECORDINGS_DIR:/recordings" --memory 8192m - -p 9222:9222 \ - -e DISPLAY_NUM=1 \ - -e HEIGHT=768 \ - -e WIDTH=1024 \ - -e RUN_AS_ROOT="$RUN_AS_ROOT" \ + -p 9222:9222 + -p 444:10001 + -e DISPLAY_NUM=1 + -e HEIGHT=768 + -e WIDTH=1024 + -e RUN_AS_ROOT="$RUN_AS_ROOT" --mount type=bind,src="$FLAGS_FILE",dst=/chromium/flags,ro ) -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - RUN_ARGS+=( -p 444:10001 ) - RUN_ARGS+=( -e WITH_KERNEL_IMAGES_API=true ) -fi - # noVNC vs WebRTC port mapping if [[ "${ENABLE_WEBRTC:-}" == "true" ]]; then echo "Running container with WebRTC" diff --git a/images/chromium-headful/run-unikernel.sh b/images/chromium-headful/run-unikernel.sh index 5911d653..37655904 100755 --- a/images/chromium-headful/run-unikernel.sh +++ b/images/chromium-headful/run-unikernel.sh @@ -41,22 +41,17 @@ trap 'rm -rf "$FLAGS_DIR"' EXIT deploy_args=( - -M 8192 + -M 4096 -p 9222:9222/tls + -p 444:10001/tls -e DISPLAY_NUM=1 -e HEIGHT=768 -e WIDTH=1024 - -e HOME=/ -e RUN_AS_ROOT="$RUN_AS_ROOT" \ -v "$volume_name":/chromium -n "$NAME" ) -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - deploy_args+=( -p 444:10001/tls ) - deploy_args+=( -e WITH_KERNEL_IMAGES_API=true ) -fi - if [[ "${ENABLE_WEBRTC:-}" == "true" ]]; then echo "Deploying with WebRTC enabled" kraft cloud inst create --start \ diff --git a/images/chromium-headful/start-chromium.sh b/images/chromium-headful/start-chromium.sh index bc1ff17c..df658215 100644 --- a/images/chromium-headful/start-chromium.sh +++ b/images/chromium-headful/start-chromium.sh @@ -24,6 +24,7 @@ export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/dbus/system_bus_socket" RUN_AS_ROOT="${RUN_AS_ROOT:-false}" if [[ "$RUN_AS_ROOT" == "true" ]]; then + echo "Running chromium as root" exec chromium \ --remote-debugging-port="$INTERNAL_PORT" \ --user-data-dir=/home/kernel/user-data \ @@ -31,6 +32,7 @@ if [[ "$RUN_AS_ROOT" == "true" ]]; then --no-first-run \ ${CHROMIUM_FLAGS:-} else + echo "Running chromium as kernel user" exec runuser -u kernel -- env \ DISPLAY=":1" \ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/dbus/system_bus_socket" \ diff --git a/images/chromium-headful/wrapper.sh b/images/chromium-headful/wrapper.sh index bd2a9dcf..dae8192a 100755 --- a/images/chromium-headful/wrapper.sh +++ b/images/chromium-headful/wrapper.sh @@ -211,68 +211,66 @@ if [[ "${ENABLE_WEBRTC:-}" == "true" ]]; then echo "[wrapper] Port 8080 is open" fi -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - echo "[wrapper] ✨ Starting kernel-images API." - - API_PORT="${KERNEL_IMAGES_API_PORT:-10001}" - API_FRAME_RATE="${KERNEL_IMAGES_API_FRAME_RATE:-10}" - API_DISPLAY_NUM="${KERNEL_IMAGES_API_DISPLAY_NUM:-${DISPLAY_NUM:-1}}" - API_MAX_SIZE_MB="${KERNEL_IMAGES_API_MAX_SIZE_MB:-500}" - API_OUTPUT_DIR="${KERNEL_IMAGES_API_OUTPUT_DIR:-/recordings}" - - # Start via supervisord (env overrides are read by the service's command) - supervisorctl -c /etc/supervisor/supervisord.conf start kernel-images-api - # close the "--no-sandbox unsupported flag" warning when running as root - # in the unikernel runtime we haven't been able to get chromium to launch as non-root without cryptic crashpad errors - # and when running as root you must use the --no-sandbox flag, which generates a warning - if [[ "${RUN_AS_ROOT:-}" == "true" ]]; then - echo "[wrapper] Running as root, attempting to dismiss the --no-sandbox unsupported flag warning" - if read -r WIDTH HEIGHT <<< "$(xdotool getdisplaygeometry 2>/dev/null)"; then - # Work out an x-coordinate slightly inside the right-hand edge of the - OFFSET_X=$(( WIDTH - 30 )) - if (( OFFSET_X < 0 )); then - OFFSET_X=0 - fi +echo "[wrapper] ✨ Starting kernel-images API." + +API_PORT="${KERNEL_IMAGES_API_PORT:-10001}" +API_FRAME_RATE="${KERNEL_IMAGES_API_FRAME_RATE:-10}" +API_DISPLAY_NUM="${KERNEL_IMAGES_API_DISPLAY_NUM:-${DISPLAY_NUM:-1}}" +API_MAX_SIZE_MB="${KERNEL_IMAGES_API_MAX_SIZE_MB:-500}" +API_OUTPUT_DIR="${KERNEL_IMAGES_API_OUTPUT_DIR:-/recordings}" + +# Start via supervisord (env overrides are read by the service's command) +supervisorctl -c /etc/supervisor/supervisord.conf start kernel-images-api +# close the "--no-sandbox unsupported flag" warning when running as root +# in the unikernel runtime we haven't been able to get chromium to launch as non-root without cryptic crashpad errors +# and when running as root you must use the --no-sandbox flag, which generates a warning +if [[ "${RUN_AS_ROOT:-}" == "true" ]]; then + echo "[wrapper] Running as root, attempting to dismiss the --no-sandbox unsupported flag warning" + if read -r WIDTH HEIGHT <<< "$(xdotool getdisplaygeometry 2>/dev/null)"; then + # Work out an x-coordinate slightly inside the right-hand edge of the + OFFSET_X=$(( WIDTH - 30 )) + if (( OFFSET_X < 0 )); then + OFFSET_X=0 + fi - # Wait for kernel-images API port to be ready. - echo "[wrapper] Waiting for kernel-images API port 127.0.0.1:${API_PORT}..." - while ! nc -z 127.0.0.1 "${API_PORT}" 2>/dev/null; do - sleep 0.5 - done - echo "[wrapper] Port ${API_PORT} is open" - - # Wait for Chromium window to open before dismissing the --no-sandbox warning. - target='New Tab - Chromium' - echo "[wrapper] Waiting for Chromium window \"${target}\" to appear and become active..." - while :; do - win_id=$(xwininfo -root -tree 2>/dev/null | awk -v t="$target" '$0 ~ t {print $1; exit}') - if [[ -n $win_id ]]; then - win_id=${win_id%:} - if xdotool windowactivate --sync "$win_id"; then - echo "[wrapper] Focused window $win_id ($target) on $DISPLAY" - break - fi + # Wait for kernel-images API port to be ready. + echo "[wrapper] Waiting for kernel-images API port 127.0.0.1:${API_PORT}..." + while ! nc -z 127.0.0.1 "${API_PORT}" 2>/dev/null; do + sleep 0.5 + done + echo "[wrapper] Port ${API_PORT} is open" + + # Wait for Chromium window to open before dismissing the --no-sandbox warning. + target='New Tab - Chromium' + echo "[wrapper] Waiting for Chromium window \"${target}\" to appear and become active..." + while :; do + win_id=$(xwininfo -root -tree 2>/dev/null | awk -v t="$target" '$0 ~ t {print $1; exit}') + if [[ -n $win_id ]]; then + win_id=${win_id%:} + if xdotool windowactivate --sync "$win_id"; then + echo "[wrapper] Focused window $win_id ($target) on $DISPLAY" + break fi - sleep 0.5 - done - - # wait... not sure but this just increases the likelihood of success - # without the sleep you often open the live view and see the mouse hovering over the "X" to dismiss the warning, suggesting that it clicked before the warning or chromium appeared - sleep 5 - - # Attempt to click the warning's close button - echo "[wrapper] Clicking the warning's close button at x=$OFFSET_X y=115" - if curl -s -o /dev/null -X POST \ - http://localhost:${API_PORT}/computer/click_mouse \ - -H "Content-Type: application/json" \ - -d "{\"x\":${OFFSET_X},\"y\":115}"; then - echo "[wrapper] Successfully clicked the warning's close button" - else - echo "[wrapper] Failed to click the warning's close button" >&2 fi + sleep 0.5 + done + + # wait... not sure but this just increases the likelihood of success + # without the sleep you often open the live view and see the mouse hovering over the "X" to dismiss the warning, suggesting that it clicked before the warning or chromium appeared + sleep 5 + + # Attempt to click the warning's close button + echo "[wrapper] Clicking the warning's close button at x=$OFFSET_X y=115" + if curl -s -o /dev/null -X POST \ + http://localhost:${API_PORT}/computer/click_mouse \ + -H "Content-Type: application/json" \ + -d "{\"x\":${OFFSET_X},\"y\":115}"; then + echo "[wrapper] Successfully clicked the warning's close button" else - echo "[wrapper] xdotool failed to obtain display geometry; skipping sandbox warning dismissal." >&2 + echo "[wrapper] Failed to click the warning's close button" >&2 fi + else + echo "[wrapper] xdotool failed to obtain display geometry; skipping sandbox warning dismissal." >&2 fi fi diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index ad23071d..e4bd956b 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -67,6 +67,8 @@ RUN set -eux; \ # Remove upower to prevent spurious D-Bus activations and logs RUN apt-get -yqq purge upower || true && rm -rf /var/lib/apt/lists/* +ENV WITHDOCKER=true + # Create a non-root user with a home directory RUN useradd -m -s /bin/bash kernel @@ -84,6 +86,5 @@ COPY images/chromium-headless/image/supervisor/services/ /etc/supervisor/conf.d/ # Copy the kernel-images API binary built in the builder stage COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api -ENV WITH_KERNEL_IMAGES_API=false ENTRYPOINT [ "/usr/bin/wrapper.sh" ] diff --git a/images/chromium-headless/image/start-chromium.sh b/images/chromium-headless/image/start-chromium.sh index 81a277c1..89d29560 100644 --- a/images/chromium-headless/image/start-chromium.sh +++ b/images/chromium-headless/image/start-chromium.sh @@ -30,6 +30,7 @@ if [[ "$RUN_AS_ROOT" == "true" ]]; then --no-first-run \ ${CHROMIUM_FLAGS:-} else + echo "Running chromium as kernel user" exec runuser -u kernel -- env \ DISPLAY=":1" \ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/dbus/system_bus_socket" \ diff --git a/images/chromium-headless/image/wrapper.sh b/images/chromium-headless/image/wrapper.sh index a7159a61..0dd79522 100755 --- a/images/chromium-headless/image/wrapper.sh +++ b/images/chromium-headless/image/wrapper.sh @@ -3,7 +3,7 @@ set -o pipefail -o errexit -o nounset # If we are outside Docker-in-Docker make sure /dev/shm exists -if [ -z "${WITH_DOCKER:-}" ]; then +if [ -z "${WITHDOCKER:-}" ]; then mkdir -p /dev/shm chmod 777 /dev/shm mount -t tmpfs tmpfs /dev/shm @@ -90,26 +90,33 @@ export CHROMIUM_FLAGS # ----------------------------------------------------------------------------- if [[ "${RUN_AS_ROOT:-}" != "true" ]]; then dirs=( + /home/kernel/user-data + /home/kernel/.config/chromium /home/kernel/.pki/nssdb /home/kernel/.cache/dconf + /tmp /var/log /var/log/supervisord ) + for dir in "${dirs[@]}"; do if [ ! -d "$dir" ]; then mkdir -p "$dir" fi done + # Ensure correct ownership (ignore errors if already correct) - chown -R kernel:kernel /home/kernel/.pki /home/kernel/.cache 2>/dev/null || true + chown -R kernel:kernel /home/kernel /home/kernel/user-data /home/kernel/.config /home/kernel/.pki /home/kernel/.cache 2>/dev/null || true else # When running as root, just create the necessary directories without ownership changes dirs=( + /tmp /var/log /var/log/supervisord /home/kernel /home/kernel/user-data ) + for dir in "${dirs[@]}"; do if [ ! -d "$dir" ]; then mkdir -p "$dir" @@ -215,15 +222,13 @@ for i in {1..100}; do sleep 0.2 done -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - echo "[wrapper] ✨ Starting kernel-images API via supervisord." - supervisorctl -c /etc/supervisor/supervisord.conf start kernel-images-api - API_PORT="${KERNEL_IMAGES_API_PORT:-10001}" - echo "[wrapper] Waiting for kernel-images API on 127.0.0.1:${API_PORT}..." - while ! (echo >/dev/tcp/127.0.0.1/"${API_PORT}") >/dev/null 2>&1; do - sleep 0.5 - done -fi +echo "[wrapper] ✨ Starting kernel-images API via supervisord." +supervisorctl -c /etc/supervisor/supervisord.conf start kernel-images-api +API_PORT="${KERNEL_IMAGES_API_PORT:-10001}" +echo "[wrapper] Waiting for kernel-images API on 127.0.0.1:${API_PORT}..." +while ! (echo >/dev/tcp/127.0.0.1/"${API_PORT}") >/dev/null 2>&1; do + sleep 0.5 +done echo "[wrapper] startup complete!" # Re-enable scale-to-zero once startup has completed (when not under Docker) diff --git a/images/chromium-headless/run-docker.sh b/images/chromium-headless/run-docker.sh index 57fa74fe..62d74337 100755 --- a/images/chromium-headless/run-docker.sh +++ b/images/chromium-headless/run-docker.sh @@ -15,15 +15,10 @@ RUN_ARGS=( --privileged --tmpfs /dev/shm:size=2g -p 9222:9222 - -e WITH_DOCKER=true + -p 444:10001 + -v "$HOST_RECORDINGS_DIR:/recordings" ) -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - RUN_ARGS+=( -p 444:10001 ) - RUN_ARGS+=( -e WITH_KERNEL_IMAGES_API=true ) - RUN_ARGS+=( -v "$HOST_RECORDINGS_DIR:/recordings" ) -fi - # If a positional argument is given, use it as the entrypoint ENTRYPOINT_ARG=() if [[ $# -ge 1 && -n "$1" ]]; then diff --git a/images/chromium-headless/run-unikernel.sh b/images/chromium-headless/run-unikernel.sh index 9d5d6282..2dda6068 100755 --- a/images/chromium-headless/run-unikernel.sh +++ b/images/chromium-headless/run-unikernel.sh @@ -8,17 +8,19 @@ source ../../shared/ensure-common-build-run-vars.sh chromium-headless kraft cloud inst rm "$NAME" || true +RUN_AS_ROOT="${RUN_AS_ROOT:-false}" + deploy_args=( --start - -M 1G + --scale-to-zero idle + --scale-to-zero-cooldown 3000ms + --scale-to-zero-stateful + -M 1024 + -e RUN_AS_ROOT="$RUN_AS_ROOT" -p 9222:9222/tls - --vcpus 1 + -p 444:10001/tls + --vcpus 2 -n "$NAME" ) -if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - deploy_args+=( -p 444:10001/tls ) - deploy_args+=( -e WITH_KERNEL_IMAGES_API=true ) -fi - kraft cloud inst create "${deploy_args[@]}" "$IMAGE" diff --git a/server/e2e/e2e_chromium_test.go b/server/e2e/e2e_chromium_test.go index 20e161bd..0b53420a 100644 --- a/server/e2e/e2e_chromium_test.go +++ b/server/e2e/e2e_chromium_test.go @@ -89,15 +89,15 @@ func ensurePlaywrightDeps(t *testing.T) { func TestChromiumHeadfulUserDataSaving(t *testing.T) { ensurePlaywrightDeps(t) - runChromiumUserDataSavingFlow(t, headfulImage, containerName, true) + runChromiumUserDataSavingFlow(t, headfulImage, containerName) } func TestChromiumHeadlessPersistence(t *testing.T) { ensurePlaywrightDeps(t) - runChromiumUserDataSavingFlow(t, headlessImage, containerName, true) + runChromiumUserDataSavingFlow(t, headlessImage, containerName) } -func runChromiumUserDataSavingFlow(t *testing.T, image, containerName string, runAsRoot bool) { +func runChromiumUserDataSavingFlow(t *testing.T, image, containerName string) { t.Helper() logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{ Level: slog.LevelInfo, @@ -111,7 +111,7 @@ func runChromiumUserDataSavingFlow(t *testing.T, image, containerName string, ru }, })) baseCtx := logctx.AddToContext(context.Background(), logger) - logger.Info("[e2e]", "action", "starting chromium cookie saving flow", "image", image, "name", containerName, "runAsRoot", runAsRoot) + logger.Info("[e2e]", "action", "starting chromium cookie saving flow", "image", image, "name", containerName) if _, err := exec.LookPath("docker"); err != nil { t.Fatalf("[precheck] docker not available: %v", err) } @@ -124,15 +124,6 @@ func runChromiumUserDataSavingFlow(t *testing.T, image, containerName string, ru t.Fatalf("[setup] failed to stop container %s: %v", containerName, err) } env := map[string]string{ - "WITH_KERNEL_IMAGES_API": "true", - "WITH_DOCKER": "true", - "RUN_AS_ROOT": fmt.Sprintf("%t", runAsRoot), - "USER": func() string { - if runAsRoot { - return "root" - } - return "kernel" - }(), "WIDTH": "1024", "HEIGHT": "768", "ENABLE_WEBRTC": os.Getenv("ENABLE_WEBRTC"), @@ -256,6 +247,14 @@ func runChromiumUserDataSavingFlow(t *testing.T, image, containerName string, ru t.Fatalf("[snapshot] upload cleaned zip: %v", err) } + // Check file state after uploading + logger.Info("[restart]", "action", "checking file state after uploading") + if err := runCookieDebugScript(ctx, t); err != nil { + logger.Warn("[restart]", "action", "post-stop debug script failed", "error", err) + } else { + logger.Info("[restart]", "action", "post-stop debug script completed") + } + // Verify that the cookie exists in the container's cookies database after upload logger.Info("[snapshot]", "action", "verifying cookie in container database", "cookieName", cookieName) if err := verifyCookieInContainerDB(ctx, cookieName); err != nil { @@ -769,7 +768,13 @@ func uploadUserDataZip(ctx context.Context, zipBytes []byte) error { return err } _, err = client.UploadZipWithBodyWithResponse(ctx, w.FormDataContentType(), &body) - return err + if err != nil { + return err + } + if _, err := execCombinedOutput(ctx, "chown", []string{"-R", "kernel:kernel", "/home/kernel/user-data"}); err != nil { + return err + } + return nil } func startChromiumViaAPI(ctx context.Context) error {