Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions desktop/packaging/kubuntu/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
7 changes: 7 additions & 0 deletions desktop/packaging/kubuntu/browser.sh
Original file line number Diff line number Diff line change
@@ -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}" "$@"
9 changes: 9 additions & 0 deletions desktop/packaging/kubuntu/session.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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: $*"; }
Expand All @@ -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.
Expand Down
10 changes: 9 additions & 1 deletion package-manager/crates/w3bs-cli/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) -> Error {
Expand Down Expand Up @@ -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}")
}
Expand Down Expand Up @@ -139,7 +142,9 @@ fn gid_of(_: &Path) -> Option<u32> {
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")])
Expand Down Expand Up @@ -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"));
}
}
Loading