diff --git a/desktop/packaging/kubuntu/Dockerfile b/desktop/packaging/kubuntu/Dockerfile index 8fe045a..3b3b6d0 100644 --- a/desktop/packaging/kubuntu/Dockerfile +++ b/desktop/packaging/kubuntu/Dockerfile @@ -35,8 +35,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ kwin-wayland plasma-desktop plasma-workspace konsole breeze breeze-cursor-theme libkscreen-bin \ xdg-desktop-portal xdg-desktop-portal-kde pipewire wireplumber dbus-daemon flatpak \ libegl-mesa0 libgl1-mesa-dri libpipewire-0.3-0t64 libjpeg-turbo8 libpixman-1-0 libpam0g \ - fonts-noto-core ca-certificates curl tini procps \ + fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji ca-certificates curl gnupg tini procps \ && rm -rf /var/lib/apt/lists/* +# A browser for agents: Google Chrome from Google's signed repository (Ubuntu's Chromium is a snap, +# and snaps do not run in a container). +RUN install -d -m 0755 /etc/apt/keyrings \ + && curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /etc/apt/keyrings/google-chrome.gpg \ + && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \ + > /etc/apt/sources.list.d/google-chrome.list \ + && apt-get update && apt-get install -y --no-install-recommends google-chrome-stable \ + && rm -rf /var/lib/apt/lists/* +COPY browser.sh /usr/local/bin/w3bs-browser # KWin asks for real-time scheduling, which a container's capability set does not grant. RUN setcap -r /usr/bin/kwin_wayland 2>/dev/null || true COPY --from=w0vncserver /usr/local/bin/w0vncserver /usr/local/bin/w0vncserver @@ -48,6 +57,6 @@ RUN arch="$(uname -m)" \ && tar xzf "$file" ./bin/w3bs-desktop && install -m 0755 bin/w3bs-desktop /usr/local/bin/ \ && rm -rf /tmp/* COPY session.sh /usr/local/bin/w3bs-kubuntu-session -RUN chmod 0755 /usr/local/bin/w3bs-kubuntu-session && mkdir -p /vnc /home/desktop && chmod 1777 /vnc /home/desktop +RUN chmod 0755 /usr/local/bin/w3bs-kubuntu-session /usr/local/bin/w3bs-browser && mkdir -p /vnc /home/desktop && chmod 1777 /vnc /home/desktop ENV HOME=/home/desktop ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/w3bs-kubuntu-session"] diff --git a/desktop/packaging/kubuntu/browser.sh b/desktop/packaging/kubuntu/browser.sh new file mode 100755 index 0000000..3943bfb --- /dev/null +++ b/desktop/packaging/kubuntu/browser.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Open Google Chrome in the agent desktop: `w3bs-browser [URL]`. +# --no-sandbox: Chrome's sandbox needs user namespaces, which this container is not given; the +# container is the boundary here. A separate profile keeps agent sign-ins out of anything else. +exec google-chrome --no-sandbox --ozone-platform=wayland --no-first-run --no-default-browser-check \ + --password-store=basic --hide-crash-restore-bubble --start-maximized \ + --user-data-dir="${W3BS_BROWSER_PROFILE:-$HOME/.config/w3bs-browser}" "$@" diff --git a/desktop/packaging/kubuntu/session.sh b/desktop/packaging/kubuntu/session.sh index 3b92c8d..81547b4 100644 --- a/desktop/packaging/kubuntu/session.sh +++ b/desktop/packaging/kubuntu/session.sh @@ -15,6 +15,12 @@ export SHELL=/bin/bash LOGS=$HOME/.local/state/w3bs-desktop/logs mkdir -p -m 0700 "$XDG_RUNTIME_DIR" && mkdir -p "$LOGS" "$XDG_CONFIG_HOME/xdg-desktop-portal" printf '[preferred]\ndefault=kde;gtk\n' > "$XDG_CONFIG_HOME/xdg-desktop-portal/kde-portals.conf" +# The KDE screenshot portal writes the image under Pictures and answers "response 2" when the +# folder does not exist; a fresh home has none. +mkdir -p "$HOME/Pictures" +# Never lock: nobody is at this screen to unlock it, and a locked session makes the desktop +# layer disable agent access ("session locked"). +printf '[Daemon]\nAutolock=false\nLockOnResume=false\nTimeout=0\n' > "$XDG_CONFIG_HOME/kscreenlockerrc" rm -f /vnc/vnc.sock say() { echo "w3bs-kubuntu: $*"; } @@ -40,6 +46,9 @@ wireplumber >"$LOGS/wireplumber.log" 2>&1 & # screen. Everything in this compositor is part of one agent desktop that the desktop layer # already lets agents see and drive, so turn the check off here; it never touches the host. export KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 +# Screenshots are a separate KWin plugin with its own check (ScreenShot2 answers +# NoAuthorized to the KDE portal here), so the desktop layer's screen capture needs it too. +export KWIN_SCREENSHOT_NO_PERMISSION_CHECKS=1 kbuildsycoca6 >"$LOGS/kbuildsycoca.log" 2>&1 || true # KWin on the virtual display; with no GPU, Mesa renders OpenGL on the CPU. diff --git a/package-manager/crates/w3bs-cli/src/desktop.rs b/package-manager/crates/w3bs-cli/src/desktop.rs index c1be824..e204835 100644 --- a/package-manager/crates/w3bs-cli/src/desktop.rs +++ b/package-manager/crates/w3bs-cli/src/desktop.rs @@ -23,6 +23,7 @@ pub const IMAGE: &str = "w3bs-kubuntu-desktop"; pub const MCP: &str = "127.0.0.1:7802"; const DOCKERFILE: &str = include_str!("../../../../desktop/packaging/kubuntu/Dockerfile"); const SESSION: &str = include_str!("../../../../desktop/packaging/kubuntu/session.sh"); +const BROWSER: &str = include_str!("../../../../desktop/packaging/kubuntu/browser.sh"); const START_TIMEOUT: Duration = Duration::from_secs(120); fn fail(code: &'static str, msg: impl Into) -> Error { @@ -50,6 +51,8 @@ pub fn image_tag() -> String { h.update(DOCKERFILE); h.update([0]); h.update(SESSION); + h.update([0]); + h.update(BROWSER); let hex: String = h.finalize().iter().take(6).map(|b| format!("{b:02x}")).collect(); format!("{IMAGE}:{hex}") } @@ -139,7 +142,9 @@ fn gid_of(_: &Path) -> Option { fn build_image(tag: &str) -> Result<()> { let ctx = data_dir()?.join("build"); std::fs::create_dir_all(&ctx).map_err(|e| fail("DESKTOP_BUILD", format!("{}: {e}", ctx.display())))?; - std::fs::write(ctx.join("Dockerfile"), DOCKERFILE).and_then(|_| std::fs::write(ctx.join("session.sh"), SESSION)).map_err(|e| fail("DESKTOP_BUILD", e.to_string()))?; + std::fs::write(ctx.join("Dockerfile"), DOCKERFILE) + .and_then(|_| std::fs::write(ctx.join("session.sh"), SESSION)) + .and_then(|_| std::fs::write(ctx.join("browser.sh"), BROWSER)).map_err(|e| fail("DESKTOP_BUILD", e.to_string()))?; eprintln!("w3bs: building the Kubuntu desktop image {tag} (first time only, a few minutes)"); let ok = Command::new("docker") .args(["build", "--quiet", "-t", tag, "-t", &format!("{IMAGE}:latest")]) @@ -310,5 +315,8 @@ mod tests { assert_eq!(t.len(), "w3bs-kubuntu-desktop:".len() + 12); assert_eq!(t, image_tag()); assert!(DOCKERFILE.contains("w0vncserver") && SESSION.contains("/vnc/vnc.sock")); + // Screen capture needs KWin's screenshot check off and a Pictures folder; agents need a browser. + assert!(SESSION.contains("KWIN_SCREENSHOT_NO_PERMISSION_CHECKS=1") && SESSION.contains("Pictures")); + assert!(DOCKERFILE.contains("google-chrome-stable") && DOCKERFILE.contains("w3bs-browser") && BROWSER.contains("google-chrome")); } }