# Minimal terminal-backend sandbox image WITH a desktop: a Python-free slim
# base plus the everyday shell tools it lacks, a TigerVNC + Xfce display stack
# served to the browser by the g4f API itself (its custom noVNC routes, /desktop/
# plus the /desktop/websockify WebSocket bridge, on the API port -- g4f/api/vnc_relay),
# and g4f-go managing its own CPython plus this checkout's g4f -- so a screen
# and the g4f API live INSIDE the sandbox instead of on the gateway host.
# The base carries no interpreter: g4f-go's embedded CPython is the image's
# only Python, exposed as python3/pip in /usr/local/bin (see below).
# Neither websockify nor Google Chrome is baked in: the g4f API's own relay
# bridges the browser to Xvnc, and the desktop menu installs & opens Chrome
# (and OmniRoute, OpenClaw, ...) on click.
#
# Multi-stage: the g4f-go launcher is compiled from source in a throwaway Go
# stage, so the image never depends on a host-side `./build-all.sh` run and the
# final image carries no Go toolchain.
#
#   docker build -f docker/Dockerfile-sandbox-desktop -t g4f-sandbox:desktop .

ARG GO_BUILDER=golang:1.25-bookworm
ARG NODE_IMAGE=node:24-bookworm-slim
ARG SANDBOX_BASE=debian:trixie-slim
# ARG BUILDPLATFORM=linux/arm64

# ---------------------------------------------------------------------------
# Stage 1: g4f-go launcher
#
# Compiled from the g4f-go/ sources rather than copied out of the repo's
# prebuilt dist/ tree, so the binary always matches the checkout. Go
# cross-compiles, so the stage runs on the build platform and emits a static
# binary for the target arch. g4f-go/bundle, g4f-go/embed and the Polli
# embedded assets are committed, so a plain `go build` works without a
# preparation step.
# ---------------------------------------------------------------------------
# hadolint ignore=DL3006
FROM --platform=$BUILDPLATFORM ${GO_BUILDER} AS g4f-go-builder

ARG TARGETOS=linux
ARG TARGETARCH

WORKDIR /src
# Dependency layer first: it only invalidates when go.mod/go.sum change.
COPY g4f-go/go.mod g4f-go/go.sum ./
RUN go mod download
COPY g4f-go/ ./
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
        go build -trimpath -ldflags "-s -w" -o /out/g4f-go .

# ---------------------------------------------------------------------------
# Stage 2: Node.js 24 distribution
#
# Staged from the official Node 24 image so the sandbox carries the newest
# Node.js 24 runtime and npm without depending on Debian's older distro package.
# ---------------------------------------------------------------------------
# hadolint ignore=DL3006
FROM ${NODE_IMAGE} AS node-dist

# ---------------------------------------------------------------------------
# Stage 3: the sandbox desktop image
# ---------------------------------------------------------------------------
# hadolint ignore=DL3006
FROM ${SANDBOX_BASE}
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]

# The sandbox runs its desktop and tools as an unprivileged user so Chrome and
# the AT-SPI bus get a real session. Its uid is configurable at build time so a
# bind-mounted home (and files created in mounted projects) line up with the
# invoking host user:
#   docker build --build-arg G4F_USER_ID="$(id -u)" ...
ARG G4F_USER_ID=1000

# G4F_VERSION gives the local checkout a PEP 440 version (setup.py reads it);
# the editable install below is marked as the builtin g4f-go package, so
# api/gui runs use it instead of PyPI's latest (see the g4f-go step below).
# The G4F_VNC_*
# pair switches on the API's custom noVNC routes: /desktop/ serves the client and
# /desktop/websockify bridges the browser WebSocket to Xvnc, both on the API port.
# PYTHONSAFEPATH keeps the working directory off sys.path for `python -m` and
# `python -c`. The API runs as `g4f-go api` from /app, so without it a stray
# /app/g4f directory would shadow the installed (editable) package. That is
# exactly how a freshly deleted/updated checkout left a running server
# importing provider modules from a path that no longer existed.
ENV DEBIAN_FRONTEND=noninteractive \
    LANG=C.UTF-8 \
    PYTHONUNBUFFERED=1 \
    PYTHONSAFEPATH=1 \
    G4F_DIR=/app \
    G4F_VERSION=0.0.0-dev \
    G4F_VNC_PORT=5900 \
    G4F_NOVNC_DIR=/usr/share/novnc \
    G4F_USER_ID=${G4F_USER_ID} \
    G4F_CONFIG_DIR=/home/pn/.g4f

# Everyday tools a slim base lacks. Kept to what agents reach for from a shell.
# Node.js 24 is copied below from node-dist because most CLIs in the sandbox
# desktop menu (docker/sandbox-menu) install through npm.
# git is in because the Hermes Agent installer clones its own checkout.
#
# The display stack is installed in the same layer: one `apt-get update` and one
# install transaction instead of two, which is both faster and smaller.
# TigerVNC plus the Xfce components a launcher starts individually, never
# xfce4-session. Xvnc is exec'd as `pn`. Neither Debian's `novnc` package nor
# websockify (pulled in as its dependency) is installed: the client files come
# from the pinned upstream release below, and the WebSocket -> VNC bridge is the
# g4f API's own /desktop/websockify route (g4f/api/vnc_relay, same port as the
# API). libnss3/libasound2/libxss1 are NOT listed: they are Chrome's runtime
# deps and the menu's Chrome .deb pulls them in itself.
# librsvg2-common is listed explicitly because it is only a Recommends of
# xfdesktop4/adwaita-icon-theme, and --no-install-recommends would drop it. It
# ships the gdk-pixbuf SVG loader (libpixbufloader_svg.so), without which GTK
# cannot render ANY svg icon -- neither the menu's per-tool icons nor the
# Adwaita symbolic category icons the .directory files name.
# yad provides the notification icon and popup menu for the g4f tray
# (sandbox-tray); it uses GTK's status icon, so the panel's stock systray
# plugin hosts it without an AppIndicator/StatusNotifier host.
RUN apt-get -o Acquire::Retries=3 update && \
    apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
        jq ripgrep fd-find tmux less nano vim-tiny zip rsync tree procps htop \
        file bsdextrautils ca-certificates curl locales sudo git iptables \
        tigervnc-standalone-server \
        xfce4-panel xfwm4 xfdesktop4 xfce4-settings xfce4-terminal \
        dbus-x11 x11-xserver-utils x11-utils x11-xkb-utils xauth fonts-dejavu-core \
        fonts-noto-core fonts-noto-color-emoji \
        at-spi2-core libgtk-3-0t64 librsvg2-common gtk-update-icon-cache yad && \
    ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
    rm -rf /var/lib/apt/lists/* \
           /var/cache/apt/* \
           /usr/share/doc/* \
           /usr/share/man/* \
           /usr/share/info/*

# Node.js 24: newest Node 24 runtime and npm copied from the official node image
# instead of Debian's older nodejs package.
COPY --from=node-dist /usr/local/bin/node /usr/local/bin/node
COPY --from=node-dist /usr/local/include/node /usr/local/include/node
COPY --from=node-dist /usr/local/lib/node_modules /usr/local/lib/node_modules

# Display stack: TigerVNC plus the Xfce components a launcher starts
# individually, never xfce4-session. Installed in the apt layer above.
# noVNC client files, fetched straight from the upstream release (pinned by
# version + sha256) instead of Debian's `novnc` package, whose dependency chain
# drags in websockify -- the standalone bridge the API's custom /desktop/ +
# /desktop/websockify routes replace. The tag tarball's tree is the same noVNC 1.6.0
# layout Debian packages, so novnc-brand.sh's verified patches below apply
# unchanged. It ships no index.html (Debian's package didn't either), so
# docker/novnc-index.html is copied on top by the branding step.
ARG NOVNC_VERSION=1.6.0
RUN ln -sf /usr/local/bin/node /usr/local/bin/nodejs && \
    ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
    ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \
    ln -sf ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack && \
    rm -rf /usr/local/lib/node_modules/npm/docs \
           /usr/local/lib/node_modules/npm/man && \
    curl -fsSL --connect-timeout 15 -o /tmp/novnc.tar.gz \
        "https://github.com/novnc/noVNC/archive/refs/tags/v${NOVNC_VERSION}.tar.gz" && \
    echo "5066103959ef4e9b10f37e5a148627360dd8414e4cf8a7db92bdbd022e728aaa  /tmp/novnc.tar.gz" \
        | sha256sum -c - && \
    mkdir -p /usr/share/novnc && \
    tar -xzf /tmp/novnc.tar.gz -C /usr/share/novnc --strip-components=1 && \
    rm /tmp/novnc.tar.gz

# Desktop menu entries for the coding CLIs (OpenCode,
# Antigravity, Kilo Code, Gemini CLI, Claude Code, ...), browsers (Google
# Chrome), local gateways/agents (OmniRoute, OpenClaw), chat UIs (LibreChat,
# LobeChat, Msty Studio, SillyTavern, Open WebUI) and VS Code Web
# (code-server), plus assistants such as Open WebUI Computer.
# sandbox-cli installs a tool on first click (vendor script / npm / pip / the
# Chrome .deb) and runs it; the .desktop files land in the panel's Applications
# menu. Each entry names its own icon (g4f-sandbox-<tool>) and passes it to
# xfce4-terminal as --icon, so the menu row and the window it opens both carry
# the vendor's mark; fetch-icons.py installs those icons further down.
# start-browser launches Chrome with the CDP debug port (9223) the g4f
# automation uses; the menu runs it with --once so it does not relaunch in a
# loop. See docker/sandbox-menu/README.md.
# The scripts themselves live in the repo's bin/ and are installed wholesale
# into the global bin dir: one COPY picks up sandbox-cli, the entrypoint, the
# firewall, start-browser and the menu helpers instead of one COPY each.
COPY bin/ /usr/local/bin/
COPY docker/sandbox-menu/g4f-sandbox-*.desktop /usr/share/applications/
COPY docker/sandbox-menu/g4f-sandbox-cli.svg /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-cli.svg
# Remote Desktop is g4f's own entry, so it is not one of fetch-icons.py's
# vendor downloads: it ships the app icon the web app itself uses (the same
# file the manifest names), installed under the menu entry's icon name.
COPY projects/remote-desktop/web/icon.svg /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-remote-desktop.svg
RUN chmod 0755 /usr/local/bin/sandbox-* /usr/local/bin/fetch-icons.py \
        /usr/local/bin/patch-xfce-menu.py /usr/local/bin/novnc-brand.sh \
        /usr/local/bin/g4f-livewall /usr/local/bin/polli-sandbox-bootstrap.sh \
        /usr/local/bin/start-browser && \
    chmod 0644 /usr/share/applications/g4f-sandbox-*.desktop \
        /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-cli.svg \
        /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-remote-desktop.svg

# The Applications button (the applicationsmenu plugin, plugin-1 of the panel's
# stock layout) carries the g4f mark instead of Xfce's stock icon. docker/g4f.svg
# is the same logo the noVNC client gets branded with below; new homes get the
# icon seeded into /etc/xdg/xfce4/panel/default.xml, and homes saved by an older
# image (their panel channel exists without a button-icon) pick it up through
# the xfconf channel default next to the menu files. Anything a user sets in
# the panel later is written to their own xfconf channel and wins.
# The visible stock categories name icons this stripped image does not ship
# (Internet, Development, Accessories, Settings), so they showed the menu's
# generic fallback; point them at symbolic marks Adwaita does carry (the same
# style the g4f submenus use). Empty categories are hidden by garcon, so the
# rest of them stays stock.
COPY docker/g4f.svg /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-applications.svg
COPY docker/sandbox-menu/panel-defaults.xfconf.xml /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml
COPY docker/sandbox-menu/desktop-directories/*.directory /usr/share/desktop-directories/
RUN sed -i 's|<property name="plugin-1" type="string" value="applicationsmenu"/>|<property name="plugin-1" type="string" value="applicationsmenu">\n      <property name="button-icon" type="string" value="g4f-sandbox-applications"/>\n    </property>|' \
        /etc/xdg/xfce4/panel/default.xml && \
    sed -i 's|^Icon=applications-internet$|Icon=web-browser-symbolic|' \
        /usr/share/desktop-directories/xfce-network.directory && \
    sed -i 's|^Icon=applications-development$|Icon=applications-engineering-symbolic|' \
        /usr/share/desktop-directories/xfce-development.directory && \
    sed -i 's|^Icon=applications-accessories$|Icon=applications-utilities-symbolic|' \
        /usr/share/desktop-directories/xfce-accessories.directory && \
    sed -i 's|^Icon=preferences-desktop$|Icon=preferences-system-symbolic|' \
        /usr/share/desktop-directories/xfce-settings.directory && \
    chmod 0644 /usr/share/icons/hicolor/scalable/apps/g4f-sandbox-applications.svg \
        /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml \
        /usr/share/desktop-directories/g4f-sandbox-*.directory && \
    patch-xfce-menu.py

# Group the entries into their own top-level menus (g4f, Browsers, Files &
# Editors, Coding Agents, Gateways, Local AI, Chat UIs, Assistants) instead of
# dumping all 51 into Development.
# The .directory files name and icon those menus; patch-xfce-menu.py inserts
# the matching <Menu> blocks into the stock xfce-applications.menu. A merged
# menu file under applications-merged/ does not work here -- garcon copies
# merged elements with a depth-2 traversal that never reaches nested <Menu>
# nodes, so the menus would be dropped. See docker/sandbox-menu/README.md.

# noVNC branding. The upstream noVNC tree ships no index.html, so a request to
# the API's /desktop/ mount would 404; novnc-index.html is a full-viewport
# page that drops straight into the autoconnected client and overlays the
# Remote Desktop viewer's touch controls: text input, modifier/navigation key
# buttons and one-tap shortcuts (g4f-controls.js/css). The g4f branding lives
# inside the stock client itself: novnc-brand.sh swaps vnc.html's logos (and
# the icon SVGs) for g4f.svg, repaints the accent palette with the g4f colors,
# stacks the connect dialog's logo above its button, bridges the browser
# clipboard to the VM and exposes the key/text bridge the overlay uses
# (app/ui.js), and defaults the viewer to remote resize (resize=remote in
# defaults.json) so the desktop matches the browser window, leaving the rest
# of noVNC untouched. It verifies each patch, so a changed noVNC layout fails
# the build.
#
# The same page is an installable PWA: the manifest and service worker live in
# the noVNC root and every URL in them is relative, so they work both at
# /desktop/ and behind desktop.g4f.local (where the host router serves the same
# files at the root). The worker caches only the shell; the VNC stream stays
# live and is never cached.
COPY docker/novnc-index.html /usr/share/novnc/index.html
COPY docker/novnc-controls.js /usr/share/novnc/g4f-controls.js
COPY docker/novnc-controls.css /usr/share/novnc/g4f-controls.css
COPY docker/novnc-manifest.webmanifest /usr/share/novnc/manifest.webmanifest
COPY docker/novnc-sw.js /usr/share/novnc/sw.js
COPY docker/g4f.svg /usr/share/novnc/g4f.svg
RUN chmod 0775 /usr/share/novnc/ -R && novnc-brand.sh

# Create the unprivileged sandbox user BEFORE the runtime install, so g4f-go
# materializes /home/pn/.g4f and every pip package as `pn` from the start --
# no root-owned runtime dir and no later chown pass. Desktop processes (Xvnc,
# Xfce and the menu's Chrome) run as `pn`: Chrome refuses to run as root
# without --no-sandbox and the AT-SPI bus wants a real user session. `pn` may
# sudo. Its uid (and matching gid) comes from G4F_USER_ID, so the host user can
# own the bind-mounted home.
RUN groupadd --gid "${G4F_USER_ID}" pn && \
    useradd --create-home --uid "${G4F_USER_ID}" --gid pn --shell /bin/bash pn && \
    echo "pn ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/pn && chmod 0440 /etc/sudoers.d/pn && \
    mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix && \
    mkdir -p /tmp/sandbox-runtime && chown pn:pn /tmp/sandbox-runtime && chmod 0700 /tmp/sandbox-runtime

# g4f-go owns the interpreter and the g4f install. The launcher built in the
# g4f-go-builder stage goes on PATH, then g4f-go materializes its runtime at
# build time and installs THIS checkout as an editable g4f (with its `[all]`
# dependencies), so the API the sandbox serves is the repository code, incl.
# g4f/api/vnc_relay. The checkout
# is copied INTO the editable project (${G4F_DIR}/gpt4free/g4f), not to a
# second /app/g4f tree: a stray /app/g4f sits on sys.path ahead of the editable
# install and shadows the live checkout (and, in the image, was root-owned and
# stale). Keeping a single source tree means `import g4f` always resolves to
# ${G4F_DIR}/gpt4free/g4f. The compose bind-mount of the repo (/app/gpt4free)
# stays live. Installing the checkout with `g4f-go install -e` marks the
# runtime as builtin (`.g4f-runtime/.builtin`), so the launcher's api/gui
# auto-upgrade leaves the checkout build alone instead of replacing it with
# PyPI's latest on the first api call.
#
# The base image ships no Python, so the embedded runtime is the only
# interpreter in the image. python3/pip in /usr/local/bin are symlinks into it
# (the pbs layout resolves its prefix through the real interpreter path, so a
# symlink works and keeps `python3 -m pip install --user` -- what sandbox-cli
# and the menu's pip tools use -- writing to ~/.local). The runtime lives in
# /home/pn/.g4f, which the `pn` user owns, so the menu can pip-install into it.
#
# Dependencies are installed in a single g4f-go call (one pip transaction).
# Pillow is one of them: the Screen pane's thumbnail is grabbed INSIDE the
# sandbox (the X socket and its cookie live here, not on the gateway host), and
# fetch-icons.py below needs it to convert JPEG sources. Installing through
# g4f-go (not the pip symlink) lands it in the embedded runtime's own
# site-packages with the launcher's PYTHONHOME/PYTHONNOUSERSITE env.
COPY --from=g4f-go-builder /out/g4f-go /usr/local/bin/g4f-go
WORKDIR ${G4F_DIR}
COPY --chown=${G4F_USER_ID}:${G4F_USER_ID} setup.py README.md MANIFEST.in ${G4F_DIR}/gpt4free/
COPY --chown=${G4F_USER_ID}:${G4F_USER_ID} g4f ${G4F_DIR}/gpt4free/g4f
# Docker creates implicitly-created parent dirs as root; pip's editable install
# writes g4f.egg-info into the project dir, so hand the whole tree to `pn`.
RUN chmod 0755 /usr/local/bin/g4f-go && \
    chown -R "${G4F_USER_ID}:${G4F_USER_ID}" "${G4F_DIR}/gpt4free"

# Run the installs as the unprivileged user: the embedded CPython, the g4f
# deps and this checkout's editable g4f all land under /home/pn/.g4f, which
# `pn` owns, so the runtime is writable by the same user the API runs as.
# HOME is passed explicitly because a USER switch alone is not guaranteed to
# update it; the root steps that follow keep the image's default HOME so they
# never create root-owned cache files inside /home/pn.
USER pn
RUN HOME=/home/pn g4f-go install -e /app/gpt4free[all] && \
    HOME=/home/pn g4f-go cache clear && \
    find /home/pn/.g4f -type d -name '__pycache__' -prune -exec rm -rf {} + && \
    find /home/pn/.g4f -type f -name '*.pyc' -delete && \
    rm -rf /home/pn/.cache && \
    rm -f /home/pn/.g4f/python-embed/.g4f-runtime/runtime-*

# Back to root for the steps that write outside the user's home: the
# python3/pip symlinks live in root-owned /usr/local/bin, and the icon steps
# below write to /usr/share. The runtime installed above stays owned by `pn`,
# so nothing needs re-chowning.
USER root
RUN ln -sf "${G4F_CONFIG_DIR}/python-embed/python-home/bin/python3.15" /usr/local/bin/python3 && \
    ln -sf "${G4F_CONFIG_DIR}/python-embed/python-home/bin/python3.15" /usr/local/bin/python && \
    ln -sf "${G4F_CONFIG_DIR}/python-embed/python-home/bin/pip3" /usr/local/bin/pip3 && \
    ln -sf "${G4F_CONFIG_DIR}/python-embed/python-home/bin/pip3" /usr/local/bin/pip && \
    fetch-icons.py && \
    gtk-update-icon-cache -f -t /usr/share/icons/hicolor && \
    printf 'XDG_RUNTIME_DIR=/tmp/sandbox-runtime\n' >> /etc/environment && \
    rm -rf /root/.cache /root/.npm /var/cache/apt/archives/* /var/lib/apt/lists/*

# Animated g4f terminal wallpaper. The entrypoint opens it in a maximized
# xfce4-terminal at desktop start; the g4f-sandbox-wallpaper menu entry can
# reopen it later. Pure stdlib Python (truecolour ANSI nebula, stars, orbits
# and a shimmering g4f emblem), so it needs nothing beyond the runtime above.
# g4f-livewall and sandbox-tray are already in /usr/local/bin via the bin/
# COPY.

# Per-tool menu icons. Every g4f-sandbox-*.desktop names g4f-sandbox-<tool> and
# hands the same name to xfce4-terminal as --icon, so the menu row and the
# window it opens share the vendor's mark. fetch-icons.py pulls each mark from
# the tool's own site (favicon / logo asset / GitHub avatar), normalizes it to
# a 128x128 PNG or a square SVG, and drops it into the hicolor theme; a tool
# whose sources are all unreachable falls back to the shared g4f-sandbox-cli.svg
# rather than failing the build. It runs after the Pillow install above because
# the JPEG sources have to be converted (GTK icon themes load png/svg/xpm only).
# gtk-update-icon-cache then refreshes the hicolor cache so the new files are
# visible to GTK without a directory rescan.

# The `pn` user (and /tmp/.X11-unix + /tmp/sandbox-runtime) was created before
# the g4f-go install above, so the runtime and packages it wrote are already
# owned by `pn` and no post-install chown is needed.

# ~/.local/bin first: sandbox-cli drops a `google-chrome` wrapper there that
# always passes --no-sandbox (see link_chrome_no_sandbox), and both
# start-browser and g4f's own browser lookup (shutil.which) must find the
# wrapper instead of the real /usr/bin binary.
# ~/.opencode/bin is where the OpenCode installer puts its binary; keeping it
# on the image PATH makes `opencode` available in any exec/terminal session
# after the menu has installed it, not only inside sandbox-cli.
ENV PATH=/home/pn/.local/bin:/home/pn/.opencode/bin:${PATH}

# Runtime dir for dbus/Xvnc; containers have no logind to create it. Both /tmp
# paths above are fixed by the X11 protocol / seeded per container, never shared.
ENV XDG_RUNTIME_DIR=/tmp/sandbox-runtime
# Dockerfile ENV reaches `docker exec` only. When this image is the target of an
# ssh backend (sshd added on top), a login session gets its environment from PAM,
# so the runtime dir must also live where pam_env reads it.

# 8080: g4f tray application and API server, which also serves the noVNC
# desktop through its custom routes: /desktop/ for the client, /desktop/websockify for
# the WebSocket bridge to Xvnc. 5900: Xvnc on display :0.
EXPOSE 8080 5900

# Egress filter for the sandbox: public internet stays reachable, but private,
# link-local and reserved destinations (the Docker host's LAN, other
# containers, cloud metadata), file-sharing protocols and crypto-mining ports
# are rejected. The entrypoint installs it before starting the desktop; the
# container needs CAP_NET_ADMIN for the rules. If the filter cannot be applied
# the desktop still starts with a warning unless SANDBOX_FIREWALL_STRICT=1.
# See bin/sandbox-firewall.sh and docker-compose.sandbox-desktop.yml.
#
# Entrypoint seeds /etc/machine-id, starts a system bus, applies the egress
# filter, launches the desktop stack (Xvnc + Xfce) as `pn` and exports DISPLAY
# so a browser on the desktop renders inside the sandbox. SANDBOX_DESKTOP=0
# skips the desktop. Both files arrive with the bin/ COPY above; this layer
# does not need a separate build-time validation step.
ENTRYPOINT ["/usr/local/bin/sandbox-entrypoint.sh"]
ENV HOME=/home/pn
USER pn
# The tray is the container's main process: it starts the g4f API as a child
# and exposes Restart Server, Update/Change g4f Package, Set API Key and a log
# link in the Xfce panel's systray. Keeping the server as a child is what makes
# a restart possible without stopping the container. The tray falls back to
# running the API directly when there is no display (SANDBOX_DESKTOP=0) or the
# tray dependencies are unavailable.
# --debug turns on g4f's verbose logging (tray parser in g4f/cli/__init__.py),
# so `docker logs` shows provider selection, request handling and HAR captures.
CMD ["sandbox-tray", "--port", "8080", "--debug"]
