diff --git a/docs/selkies/.pages b/docs/selkies/.pages new file mode 100644 index 00000000..9c63d555 --- /dev/null +++ b/docs/selkies/.pages @@ -0,0 +1,5 @@ +nav: + - Overview: index.md + - User Guide: user-guide + - Developer Guide: developer-guide + - Components: components diff --git a/docs/selkies/components/.pages b/docs/selkies/components/.pages new file mode 100644 index 00000000..926b8ce8 --- /dev/null +++ b/docs/selkies/components/.pages @@ -0,0 +1,9 @@ +nav: + - index.md + - Selkies: selkies.md + - Pixelflux and Pcmflux: pixelflux.md + - Selkies Baseimages: baseimages.md + - Selkies Desktop: selkies-desktop.md + - SealSkin: sealskin.md + - SealSkin Apps Registry: sealskin-apps.md + - Pelorus: pelorus.md diff --git a/docs/selkies/components/baseimages.md b/docs/selkies/components/baseimages.md new file mode 100644 index 00000000..77011624 --- /dev/null +++ b/docs/selkies/components/baseimages.md @@ -0,0 +1,75 @@ +# Selkies Baseimages + +**Repository:** [linuxserver/docker-baseimage-selkies](https://github.com/linuxserver/docker-baseimage-selkies) · **Registries:** `ghcr.io/linuxserver/baseimage-selkies`, `lsiobase/selkies` + +`docker-baseimage-selkies` is the packaging layer of the platform, the image every Webtop and single application container is built FROM. It assembles the whole runtime, the compositor stack, Selkies, pixelflux and pcmflux, Nginx, PulseAudio, GPU detection, hardening, and the LinuxServer.io s6 service machinery, so that a downstream image only has to install an application and say how to start it. + +This page is the component overview. The [Developer Guide](../developer-guide/index.md) covers building on it ([Building Custom Images](../developer-guide/building-images.md)) and its internals in depth ([Baseimage Internals](../developer-guide/baseimage-internals.md)). + +## Available distros + +One branch and tag per base distribution, all for x86_64 and aarch64: + +| Distro | Tag | +| --- | --- | +| Alpine | `alpine324` | +| Arch | `arch` | +| Debian | `debiantrixie` | +| Fedora | `fedora44` | +| Kali | `kali` | +| Ubuntu | `ubunturesolute` | + +There is deliberately **no `latest` tag** for base images. Downstream images pin a distro tag, for example `FROM ghcr.io/linuxserver/baseimage-selkies:debiantrixie`. + +## What is inside + +- **The LSIO foundation**: each tag builds on the corresponding LinuxServer.io distro baseimage, inheriting s6-overlay init, the `abc` user with PUID and PGID remapping, `TZ`, Docker mods, and the `/custom-cont-init.d` and `/custom-services.d` hooks. +- **Selkies** (pinned commit) installed into the `/lsiopy` virtualenv, with pixelflux 2.x and pcmflux 2.x, plus [Pelorus](pelorus.md) preinstalled. +- **The web client**: prebuilt dashboards under `/usr/share/selkies/`, selected at runtime by the `DASHBOARD` variable. +- **Compositors for both stacks**: labwc (built from source with a small IPC patch that adds the window query socket Pelorus uses) for Wayland, and a patched Xvfb (with `-vfbdevice` DRI3 support) plus Openbox for the legacy X11 fallback. A patched wlroots build makes the compositor survive pixman rendering faults instead of crashing. +- **[Selkies Desktop](selkies-desktop.md)** at `/usr/bin/selkies-desktop`, activated by env var. +- **Nginx** with the fancyindex module, serving the client, proxying the WebSocket, handling basic auth, subfolder support, and the `/files` download index. +- **PulseAudio** with null sinks (`output` and `input`) wired for stream audio and microphone return. +- **Gamepad plumbing**: the joystick interposer and fake udev libraries, preloaded globally, with device nodes created at init. +- **Quality of life**: passwordless sudo for the desktop user, all system locales prebuilt for `LC_ALL`, `proot-apps` synced into the user home for persistent app installs, Docker in Docker support for privileged containers, and notification support. + +## The runtime in one diagram + +```mermaid +graph TD + subgraph s6 services + NGINX[svc-nginx] + PULSE[svc-pulseaudio] + SELKIES[svc-selkies: the selkies server] + DE[svc-de: startwm script] + WD[svc-watchdog: RESTART_APP] + end + SELKIES -->|starts in process| PF[pixelflux Wayland compositor, socket wayland-1] + DE -->|waits for wayland-1| LABWC[labwc or a full DE, exposes wayland-0] + LABWC --> APP[autostart application] + NGINX -->|3000 / 3001| WEB[web client, /websocket proxy, /files, /pelorus] +``` + +At startup a chain of one shot init scripts configures everything from environment variables: Nginx substitution (ports, auth, subfolder, title), Wayland or X11 mode selection, first run copy of the autostart and menu defaults into `/config`, hardening (the `HARDEN_*` and `DISABLE_*` family), GPU detection and permission fixes, and gamepad device setup. Then the long running services above come up in dependency order. + +## The two session modes + +- **Wayland (default on capable hardware)**: pixelflux hosts the virtual compositor; labwc (single apps) or a full desktop (Webtop flavors) nests on it; zero copy GPU encoding is available. `PIXELFLUX_WAYLAND=true` is baked into current downstream images. +- **X11 (legacy fallback)**: patched Xvfb with DRI3, Openbox, XSHM capture. Selected with `PIXELFLUX_WAYLAND=false` or on flavors that have not moved to Wayland yet. Deprecated for GPU work. + +## The downstream contract + +A downstream image customizes the base by providing a handful of well known files: + +| File | Purpose | +| --- | --- | +| `/defaults/autostart_wayland` and `/defaults/autostart` | The command that launches your app (Wayland and X11 variants) | +| `/defaults/menu_wayland.xml` and `/defaults/menu.xml` | The right click root menu | +| `/defaults/startwm_wayland.sh` and `/defaults/startwm.sh` | Replace the whole session for full desktop images | +| `/usr/share/selkies/www/icon.png` | The app icon used for the PWA and favicon | + +Plus `ENV TITLE`, and whatever packages the app needs. That is the entire interface, [Building Custom Images](../developer-guide/building-images.md) walks through real examples. + +## Versioning and builds + +Images rebuild on a weekly package check cadence and on baseimage changes, through the standard LinuxServer.io Jenkins pipeline. Each downstream repository (chromium, webtop, and the rest of the fleet) is triggered from its own upstream signal, app version bumps or OS package updates, so the whole catalog stays current without manual intervention. diff --git a/docs/selkies/components/index.md b/docs/selkies/components/index.md new file mode 100644 index 00000000..c4b2b3bb --- /dev/null +++ b/docs/selkies/components/index.md @@ -0,0 +1,66 @@ +# Components + +The platform is built from focused subprojects, each independently useful, each documented here with what it is, how it works, and where it sits in the stack. + +## The map + +```mermaid +graph TD + subgraph Clients + BR[Web browser client] + EXT[SealSkin extension and mobile apps] + AI[LLM agents] + end + subgraph Orchestration + SS[SealSkin server] + REG[SealSkin apps registry] + end + subgraph "App containers (one per session)" + NG[Nginx] + SEL[Selkies server] + PF[Pixelflux video] + PCM[Pcmflux audio] + PEL[Pelorus] + WM[labwc or KDE Plasma] + SD[Selkies Desktop shell] + APP[Application] + end + BI[docker-baseimage-selkies] -.packages everything above the line.-> NG + EXT --> SS + SS --> REG + SS -->|launches| NG + BR --> NG + AI --> PEL + NG --> SEL + SEL --> PF + SEL --> PCM + PF --> WM + WM --> APP + SD -.optional shell.-> WM + PEL -.optional agent API.-> WM +``` + +## The projects + +| Component | One line | Layer | +| --- | --- | --- | +| [Selkies](selkies.md) | The streaming server and web client: WebSocket protocol, input, audio, clipboard, files, sharing | Session server | +| [Pixelflux and Pcmflux](pixelflux.md) | Capture and encoding engines for video (including the in process Wayland compositor) and audio | Media pipeline | +| [Selkies Baseimages](baseimages.md) | The OCI baseimages that package the whole stack with s6, Nginx, GPU detection, and LSIO conventions | Packaging | +| [Selkies Desktop](selkies-desktop.md) | A tiny panel, start menu, taskbar, and desktop icon shell for single app containers | Optional shell | +| [SealSkin](sealskin.md) | Self hosted orchestration: users, auth, on demand container launching, extensions, and mobile apps | Orchestration | +| [SealSkin Apps Registry](sealskin-apps.md) | The YAML app manifests and autostart scripts that define the launchable catalog | Orchestration data | +| [Pelorus](pelorus.md) | Agentic interface: text based desktop state and a control API so LLMs can drive sessions | Agent layer | + +## How a frame reaches your eyeball + +To make the layering concrete, here is the life of one frame in a Wayland mode container: + +1. The application renders into a buffer belonging to **labwc** (or KWin on KDE), which is itself a client of the headless Smithay compositor that **pixelflux** hosts in process. +2. Pixelflux composites the output. If a GPU holds the framebuffer and the encoder lives on the same GPU, the frame is passed as a DMA-BUF straight into NVENC or VA-API, zero copy. Otherwise it is read back and encoded on CPU, in parallel stripes if the software encoder is in use. +3. Only regions that changed get encoded at all; a static screen costs almost nothing, and after motion stops a high quality paint over pass restores perfect detail. +4. The encoded frame, with a small binary header, is handed to **Selkies**, which broadcasts it over the WebSocket to every connected viewer with backpressure control per client. +5. The container's **Nginx** carries that WebSocket alongside the static web client, file downloads, basic auth, and the optional Pelorus API, all on one HTTPS port. +6. In your browser, the **web client** decodes with WebCodecs and paints to a canvas, while sending your input, clipboard, mic, and gamepad state back up the same socket. + +Everything above ships in one container image built on **docker-baseimage-selkies**, and **SealSkin** launches, secures, and reaps such containers on demand. diff --git a/docs/selkies/components/pelorus.md b/docs/selkies/components/pelorus.md new file mode 100644 index 00000000..e52c1415 --- /dev/null +++ b/docs/selkies/components/pelorus.md @@ -0,0 +1,81 @@ +# Pelorus + +**Repository:** [linuxserver/pelorus](https://github.com/linuxserver/pelorus) · **Enable with:** `-e PELORUS=true` · **UI at:** `https://:3001/pelorus/` + +Pelorus is the agentic interface for Selkies desktops: an AI navigator that lets a chat LLM drive a real Linux desktop. It works with labwc sessions (single application containers) and full KDE Plasma desktops, and it is preinstalled in every Selkies baseimage, activated with a single environment variable. + +The key idea that separates Pelorus from screenshot driven "computer use" agents: **the desktop is represented as text.** Window lists with exact geometry, accessibility trees with precise click coordinates for every button and menu item, taskbar and start menu contents, all delivered as structured text. A model never has to guess pixel coordinates from an image, which means even small quantized local models can operate the desktop reliably. Screenshots exist as a fallback for content that has no accessibility representation (games, canvases, some Electron apps). + +## Enabling it + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + -e PELORUS=true \ + lscr.io/linuxserver/chromium:latest bash +``` + +`PELORUS=true` in Wayland mode does all of this automatically: + +- Starts the Pelorus FastAPI server (port 5100 internally, proxied by the container's Nginx at `/pelorus/`) +- Enables the pixelflux **Computer Use API** on port 5000 internally (`PIXELFLUX_CU=5000`), the raw input injection and screenshot layer +- Starts the AT-SPI accessibility registry and forces the accessibility bridge on for GTK and Qt applications so their UI trees are readable +- On labwc, starts the compositor with its IPC socket (`labwc -i`) so windows can be enumerated; on KDE, window data comes from KWin over D-Bus + +Open `https://:3001/pelorus/` for the built in chat UI, or `.../pelorus/docs` for the OpenAPI reference. + +## How the agent loop works + +1. **Observe:** capture desktop state as text, the environment header, desktop icons, taskbar buttons, one line per window with PID, title, and geometry, plus any open start menu contents. +2. **Infer:** send state, system prompt, and the task to the configured model. Supported providers: **Ollama**, any **OpenAI compatible** endpoint, and **Gemini**. +3. **Act:** dispatch the model's tool calls, input actions go to the Computer Use backend, introspection actions are answered locally. +4. Repeat until the task completes or the step budget runs out. + +The model gets three tools: + +- **`computer`**: the workhorse. Actions include `desktop_state`, `explore_window` (dump a window's full accessibility tree with exact screen coordinates for every element), `key`, `type`, the click family, `scroll`, `left_click_drag`, `hold_key`, `wait`, and `close_window`. `explore_window` is the primary inspection tool; a vision screenshot of the window region is the fallback when a tree is unavailable. +- **`create_task`** and **`set_task_status`**: a small task queue so the agent can decompose work. + +## Using Pelorus from your own code + +You do not have to use the built in agent. The REST API exposes the desktop primitives directly, so an external agent (or your own orchestration) can be the brain. The full API reference with request and response schemas lives in [API.md in the Pelorus repository](https://github.com/linuxserver/pelorus/blob/master/API.md); the highlights: + +| Endpoint | Purpose | +| --- | --- | +| `POST /api/run` | Run the built in agent on a task, optionally streaming steps over SSE | +| `GET /api/state` | The text based desktop state | +| `GET /api/windows` | Window list with geometry | +| `GET /api/desktop/explore/{pid}` | Accessibility tree for a window, coordinates enriched to absolute screen positions | +| `POST /api/desktop/control` | Raw input: clicks, typing, keys, scrolling, drags, screenshots, region zoom | +| `GET /api/desktop/screenshot` | Full desktop screenshot, base64 PNG | +| `POST /api/desktop/close/{pid}` | Close a window | +| `WS /ws` | Streaming agent protocol used by the chat UI | + +Provider configuration lives at `/config/agent/config.toml` (auto created, seeded from `PELORUS_PROVIDER`, `PELORUS_ENDPOINT`, `PELORUS_MODEL`, and `PELORUS_API_KEY` environment variables), and can be managed at runtime through `/api/servers`. + +## Configuration reference + +| Variable | Default | Description | +| --- | --- | --- | +| `PELORUS` | unset | Set `true` in a baseimage container to enable the whole stack | +| `PELORUS_PORT` | `5100` | Internal API port | +| `PELORUS_PROVIDER` | `ollama` | `ollama`, an OpenAI compatible provider, or `gemini` | +| `PELORUS_ENDPOINT` | `http://localhost:11434` | Model endpoint URL | +| `PELORUS_MODEL` | `gemma4:12b` | Model name | +| `PELORUS_API_KEY` | empty | API key where the provider needs one | +| `PIXELFLUX_CU` | `5000` when `PELORUS=true` | Computer Use API port, set automatically | + +## Security + +Treat Pelorus as an unauthenticated root of control over the session: + +- The API has **no authentication of its own**. In the containers it is reachable only through Nginx (which can carry the container's basic auth) and is not published directly. Never expose port 5100 or 5000 yourself. +- Configured provider API keys are stored in `/config/agent/config.toml` and are readable through the server management API, anyone who can reach the API can read them. +- Enabling the accessibility bridge means every process in the session can read the full UI text of every application. That is inherent to how AT-SPI works and is the price of text based desktop state. + +The [Security and Hardening](../user-guide/security.md) page covers the container level mitigations. + +## How it fits the stack + +Pelorus is a consumer of two lower layers documented elsewhere: the pixelflux [Computer Use API](pixelflux.md) for input and pixels, and the compositor's window enumeration (the labwc IPC patch shipped in the [baseimages](baseimages.md), or KWin D-Bus on KDE). It adds the accessibility layer, the state formatting, the LLM providers, and the agent loop on top. If you are building your own agentic system, that layering is the map: you can plug in at any level. diff --git a/docs/selkies/components/pixelflux.md b/docs/selkies/components/pixelflux.md new file mode 100644 index 00000000..9f0a7dca --- /dev/null +++ b/docs/selkies/components/pixelflux.md @@ -0,0 +1,94 @@ +# Pixelflux and Pcmflux + +**Repository:** [linuxserver/pixelflux](https://github.com/linuxserver/pixelflux) · **PyPI:** `pip install pixelflux` · **License:** MPL-2.0 + +Pixelflux is the rendering and capture pipeline at the core of the entire platform. It captures a Linux framebuffer, decides what changed, encodes it as JPEG or H.264, and hands the encoded frames to your code through a Python callback. Selkies is its primary consumer, but it is a standalone library you can embed in anything. + +Pcmflux is its audio sibling: it captures PulseAudio output and encodes Opus frames for delivery to the browser, and handles the microphone return path. Both ship as prebuilt wheels for x86_64 and aarch64 on glibc and musl. + +## What it actually is + +As of the 2.0.0 release, pixelflux is a **Rust** library exposed to Python through PyO3 (earlier versions were C++ with ctypes). One extension module contains: + +- An **X11 backend**: XShm screen capture with XFixes cursor tracking, for the legacy X11 stack. +- A **Wayland backend**: a full headless Wayland compositor built on [Smithay](https://github.com/Smithay/smithay), running in process. This is the key architectural point of the modern stack: **pixelflux does not capture a Wayland compositor, it is the compositor.** It synthesizes the output, seat, and clipboard itself, which is why the framebuffer can live directly on a GPU and why input is injected through its API rather than tools like xdotool. +- Four encoders and the damage tracking, paint over, and rate control logic shared between them. + +## Encoders + +| Encoder | Hardware | Mode | Notes | +| --- | --- | --- | --- | +| JPEG | CPU | Striped | Stateless, per stripe quality. Kept as the compatibility path for browsers that cannot decode video frames; 4:4:4 H.264 with paint over matches it visually everywhere else | +| x264 | CPU | Striped or full frame | Default software H.264, ultrafast zerolatency, 4:4:4 capable | +| OpenH264 | CPU | Full frame only | Opt in alternative software encoder, 4:2:0 only | +| NVENC | Nvidia GPU | Full frame | Direct NVENC via runtime library loading, no CUDA toolkit needed, supports High 4:4:4, zero copy from DMA-BUF | +| VA-API | Intel and AMD GPU | Full frame | Through FFmpeg's `h264_vaapi`, zero copy from DMA-BUF, no 4:4:4 (falls back to CPU) | + +### Striped encoding + +On the CPU paths the screen is divided into horizontal stripes, one per CPU core (each at least 64 rows). Each stripe has its own encoder instance and its own damage history, and stripes are encoded in parallel across a thread pool. Only stripes that changed are encoded and sent. This is the "hybrid VNC and video codec" idea that lets a CPU only server idle at nearly zero cost and still deliver 60fps where the screen is actually moving. + +Hardware encoders always operate full frame, delivered as a single full height stripe, since the GPU encodes the whole surface in one shot anyway. + +### Damage detection + +- **X11:** each stripe's pixels are hashed (xxh3) every frame, a changed hash marks the stripe dirty. Continuously changing regions enter a "damage block" state that skips re hashing for a configured number of frames to save CPU. +- **Wayland:** no hashing needed, the compositor knows exactly which rectangles clients damaged and maps them to stripes. +- A fully idle screen takes a fast path that skips the encode thread pool entirely. + +### Paint over + +The signature quality feature. After a region has been static for a configurable number of frames (`paint_over_trigger_frames`), it is re sent at high quality: a higher quality JPEG, or for H.264 a burst of frames at a lower CRF. Motion cancels an in flight burst. The result is video efficiency during motion and pixel perfect text the moment you stop scrolling. + +### Rate control + +- **CRF/CQP mode** (default): constant quality, bits go wherever needed. +- Infinite GOP by default: keyframes are only sent on demand (client join, recovery, or an optional periodic interval). Bitrate, framerate, and quality are all adjustable live without restarting the capture. + +## Zero copy on Wayland + +When the compositor renders on a GPU and the encoder is on the same GPU, frames flow as DMA-BUF handles from the render buffer straight into NVENC or VA-API. The pixels never touch system RAM and the CPU never sees them. If the render and encode devices differ, or a software encoder is selected, pixelflux falls back to a readback path automatically and logs which decision it made. + +GPU selection is automatic: it walks `/sys/class/drm`, identifies cards by driver (`nvidia` goes to NVENC, `i915` and `amdgpu` to VA-API), and can be pinned by device path, index, or an `auto_gpu` token matching a driver or vendor ID. + +## API sketch + +```python +from pixelflux import CaptureSettings, ScreenCapture, ensure_wayland_display + +settings = CaptureSettings() +settings.capture_width = 1920 +settings.capture_height = 1080 +settings.target_fps = 60.0 +settings.output_mode = 1 # 0 = JPEG, 1 = H.264 +settings.video_crf = 25 +settings.use_paint_over_quality = True + +def on_frame(frame): + # frame is a StripeFrame: zero copy buffer protocol object + # frame.data_type: 1 = JPEG, 2 = H.264 + # bytes(frame) or memoryview(frame) for the payload + ws.send(bytes(frame)) + +capture = ScreenCapture() +capture.start_capture(on_frame, settings) +``` + +Each encoded stripe arrives with a compact binary header (6 bytes for JPEG, 10 for H.264 carrying frame type, frame number, stripe offset, and dimensions) that the web client parses to place stripes on the canvas. Headers can be omitted for embedding in your own protocol. + +The Wayland backend additionally exposes input injection (keyboard by scancode with a hot swappable XKB keymap, absolute and relative pointer, buttons, scroll), clipboard get and set, cursor callbacks delivering PNG cursor images out of band, and live rate updates. `example/screen_to_browser.py` in the repository is a complete working WebSocket streaming server in one file, with a matching browser client in `example/index.html`. + +## Extras worth knowing about + +- **Computer Use API**: setting the `PIXELFLUX_CU=` environment variable starts a small HTTP server with a `POST /computer-use` endpoint accepting JSON actions (`screenshot`, `left_click`, `type`, `key`, `scroll`, `zoom`, and friends). This is the raw input and vision layer that [Pelorus](pelorus.md) builds on. +- **Recording sink**: point `recording_socket` (or `PIXELFLUX_RECORDING_SOCKET`) at a Unix socket path and pixelflux serves the raw Annex-B H.264 bitstream to any connected client, forcing a keyframe when someone connects. Requires a full frame H.264 mode. +- **Watermarking**: composite a PNG over the stream at any corner, centered, or animated (it bounces). On the GPU path the watermark is composited before encode with no readback penalty. +- **Fractional scaling**, HiDPI support, and cursor theme control on the Wayland backend. + +## Selkies environment variable mapping + +The library itself is configured purely through `CaptureSettings`. The familiar container variables (`DRINODE`, `DRI_NODE`, `AUTO_GPU`, `PIXELFLUX_WAYLAND`, `SELKIES_*`) are read by Selkies and the baseimage init scripts, which translate them into settings fields. Keep that separation in mind when embedding pixelflux directly. + +## Building from source + +`pip install .` drives a `setuptools-rust` build. On Debian or Ubuntu the build dependencies are roughly: `python3-dev cmake nasm libclang-dev libavcodec-dev libavutil-dev libx264-dev libturbojpeg0-dev libgbm-dev libdrm-dev libwayland-dev libinput-dev libxkbcommon-dev libva-dev` plus a Rust toolchain. Any system FFmpeg from 6.0 through 8.1 works (only used for VA-API). Most users should just take the prebuilt wheels. diff --git a/docs/selkies/components/sealskin-apps.md b/docs/selkies/components/sealskin-apps.md new file mode 100644 index 00000000..08b78205 --- /dev/null +++ b/docs/selkies/components/sealskin-apps.md @@ -0,0 +1,77 @@ +# SealSkin Apps Registry + +**Repository:** [linuxserver/sealskin-apps](https://github.com/linuxserver/sealskin-apps) + +The apps registry is the data that powers the [SealSkin](sealskin.md) app store: a single YAML manifest describing every launchable application, plus the autostart scripts that wire file and URL handoff into each app. It is intentionally just text files on GitHub, no database, no packaging format, which means **anyone can host their own store** by publishing two things: an `apps.yml` and an `autostart/` directory next to it. + +The default store consumed by every SealSkin server is: + +``` +https://raw.githubusercontent.com/linuxserver/sealskin-apps/refs/heads/master/apps.yml +``` + +Admins can add any number of additional stores by URL, so an organization, university, or community can curate its own catalog while still layering on the official one. + +## apps.yml structure + +The manifest has two root keys. First, `extension_groups`: reusable YAML anchors grouping file extensions (common images, RAW images, vectors, audio, video, MS Office, OpenDocument, ebooks, CAD and 3D, disc images, archives, code). Second, `apps`: the list itself, currently just over one hundred entries. A representative record: + +```yaml +- id: "zotero" + name: "Zotero" + logo: "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/zotero-icon.png" + url: "https://github.com/linuxserver/docker-zotero" + provider: "docker" + provider_config: + image: "lscr.io/linuxserver/zotero:latest" + port: 3000 + nvidia_support: true + dri3_support: true + type: "app" + autostart: true + url_support: false + open_support: true + extensions: [ris, bib, rdf] +``` + +Field meanings: + +| Field | Meaning | +| --- | --- | +| `id`, `name`, `logo`, `url` | Identity, display name, icon URL, upstream project link | +| `provider` | Launch backend, `docker` today | +| `provider_config.image` | The container image, always a Selkies based `lscr.io/linuxserver/*` image | +| `provider_config.port` | The internal web port SealSkin proxies to (3000 for nearly everything) | +| `nvidia_support` / `dri3_support` | Which GPU types the app can use, gates the GPU picker in the launcher | +| `type` | `app`, `browser`, or `desktop`, drives client behavior (browsers get URL handoff, desktops skip autostart) | +| `autostart` | Whether an autostart script exists for this app | +| `url_support` / `open_support` | Whether the app can receive a URL or an opened file at launch | +| `extensions` | File extensions this app volunteers to handle, referencing the extension groups | +| `docker_overrides` | Optional raw Docker options an app needs (for example Steam ships `security_opt: [seccomp=unconfined, apparmor=unconfined]`) | + +The catalog spans the whole [application fleet](../user-guide/apps.md): a dozen browsers, 24 full desktops (Kali plus the Webtop matrix of distro and desktop combinations), creative tools, office suites, development environments, and a deep emulation and gaming shelf. + +## The autostart scripts + +For every app with `autostart: true`, the `autostart/` directory holds two scripts: `` for X11 sessions and `-wayland` for Wayland sessions. Before launching a container, the SealSkin server writes the appropriate script into the session's home directory as the window manager autostart file. + +This is the mechanism behind "click a link, it opens isolated" and "download a file, it opens in the right app": SealSkin passes `SEALSKIN_URL` or `SEALSKIN_FILE` as environment variables, and the scripts consume them: + +```bash +/usr/bin/firefox ${FIREFOX_CLI} ${SEALSKIN_FILE:+"$SEALSKIN_FILE"} ${SEALSKIN_URL:+"$SEALSKIN_URL"} +``` + +Scripts can also do first run setup, seeding emulator configs, installing desktop entries, starting helper panels, whatever the app needs to feel ready the moment the stream appears. + +## Hosting your own store + +1. Write an `apps.yml` following the schema above (a bare list or an `apps:` keyed document both work). +2. Put autostart scripts in an `autostart/` directory *next to* the manifest; SealSkin derives their URLs from the manifest URL (`/autostart/` and `-wayland`). +3. Host both anywhere that serves raw files over HTTPS (a GitHub repository is perfect). +4. Add the manifest URL as a store in the SealSkin admin UI. + +Stores are cached server side with ETag revalidation and refreshed hourly, so updates propagate automatically. Custom store apps can point at any image, including your own images built with the [Developer Guide](../developer-guide/building-images.md), which makes the registry the natural distribution channel for in house apps on a SealSkin deployment. + +## Related lists + +The website app showcase at [sealskin.app](https://sealskin.app) maintains a compact JavaScript list of the fleet (`apps.js`) for display purposes; the YAML manifest in this repository is the operational source of truth that servers actually consume. diff --git a/docs/selkies/components/sealskin.md b/docs/selkies/components/sealskin.md new file mode 100644 index 00000000..d48e26f4 --- /dev/null +++ b/docs/selkies/components/sealskin.md @@ -0,0 +1,74 @@ +# SealSkin + +**Repository:** [selkies-project/sealskin](https://github.com/selkies-project/sealskin) · **Server image:** [linuxserver/docker-sealskin](https://github.com/linuxserver/docker-sealskin) · **Site:** [sealskin.app](https://sealskin.app) + +SealSkin is the orchestration layer of the platform: a self hosted client server system that manages users and launches Selkies containers on demand. It is two things at once: + +1. **A turnkey VDI platform.** Install the server, install the browser extension or mobile app, and you have multi user, on demand, GPU aware application streaming with authentication, file management, and collaboration built in. +2. **A reference implementation.** If you want to build your own VDI product, kiosk fleet, or internal tool on the Selkies images, SealSkin's source shows exactly how to launch, configure, proxy, and reap these containers correctly. Every trick it uses is documented below. + +The philosophy: **the browser is your operating system.** The extension intercepts what you do, clicking a link, downloading a file, right clicking an image, and redirects the action into an isolated container on your server. Suspicious files never touch your device, heavyweight apps run on server hardware, and sessions are ephemeral by default. + +## Architecture + +```mermaid +graph LR + E[Browser extension / mobile app] -->|E2EE REST API| A[API server, FastAPI] + E -->|HTTPS sessions| C[Caddy session proxy] + A -->|docker socket| D[Docker Engine] + D --> S1[Selkies app container] + D --> S2[Selkies app container] + C -->|per session auth| S1 + C -->|per session auth| S2 +``` + +- **Control plane:** a Python FastAPI server (default port 8000). Handles the handshake, auth, app and user management, and session orchestration. All API request and response bodies are end to end encrypted with an AES-GCM session key exchanged via RSA, on top of whatever transport is in play. +- **Data plane:** a bundled Caddy instance (default port 8443, TLS with your certificate) proxies live session traffic. Containers are never exposed directly; Caddy asks the API server to resolve each session path to the right container IP and injects the container's per session basic auth credentials upstream. +- **Providers:** container launching is behind a provider abstraction, Docker via the socket is the implemented provider. This is the seam you would extend for Kubernetes or remote hosts. + +## Authentication: no passwords, anywhere + +SealSkin uses RSA key pairs instead of passwords. Each user has a public key on the server; the client signs a short lived JWT (5 minute expiry) with its private key for every API interaction. The server verifies against the stored public key. If the server is compromised there are no password hashes to steal. On first start the server generates an `admin` user and writes a ready to import `admin.json` client configuration containing the key material. + +Users and groups are plain files on disk with per user settings: persistent storage on or off, GPU access, public sharing, hardening flags. Group settings override user settings. Admins get a full management UI inside the extension's options page. + +## How it launches Selkies containers + +This is the part to study if you are building your own platform. For each session SealSkin: + +1. Generates a session UUID and random `CUSTOM_USER` and `PASSWORD` credentials (a fresh pair per session). +2. Injects environment variables: `SUBFOLDER=//` (so the container serves under its session path), `PUID`, `PGID`, `PIXELFLUX_WAYLAND`, `LC_ALL` for the user's language, and `DRINODE` plus `DRI_NODE` when a GPU is assigned. +3. Applies the app template: an admin defined bundle of the standard Selkies environment variables (UI lockdown, hardening, encoder settings, watermarks), plus `DOCKER_*` pseudo variables that translate into Docker options (privileged, devices, memory limits, network mode, and so on). +4. Mounts the home directory: a persistent per user home under `/storage//`, or an ephemeral directory that is deleted when the session ends. A shared files mount lands at `/config/Desktop/files`. +5. Writes the app's **autostart script** into the mounted home before boot (`.config/labwc/autostart` for Wayland), which is how "open this URL" and "open this file" work: the script consumes `SEALSKIN_URL` or `SEALSKIN_FILE` variables. Scripts come from the [apps registry](sealskin-apps.md). +6. Starts the container with no published ports on the server's Docker network, waits for the web interface to answer, then hands the client a session URL through the Caddy proxy. +7. On session end, stops the container (containers run with auto remove) and deletes ephemeral storage. + +GPU allocation is automatic: the server detects render nodes and drivers at startup, users with GPU permission pick a device at launch, Nvidia cards get the runtime and device requests wired up, Intel and AMD get the DRI device mapped. + +## Features beyond launching + +- **Collaboration rooms:** launch a session in room mode and invite participants with links, full control, read only, or gamepad player slots, with A/V chat signalling and up to four physical gamepads passed through. Built on the Selkies token control plane (port 8083 inside the container). +- **File manager:** browse, upload, and download files in your server side homes from the extension, with chunked transfers for large files. +- **Public shares:** password protectable, expiring public download links for files in your storage. +- **App Lab (meta apps):** launch a base app in customize mode, install software and tweak settings interactively, then commit the home directory as a golden template. New "meta apps" launch from copies of that template, no Docker knowledge required. +- **App templates:** reusable environment variable bundles for hardening and UI lockdown, applied per app from the admin UI. +- **Download interception:** the extension can intercept your next browser download and reroute the file into an isolated container instead of your disk. +- **Internationalization:** the client is localized in 18 languages, and sessions launch with the user's locale via `LC_ALL`. + +## Clients + +- **Browser extension** ([Chrome](https://chromewebstore.google.com/detail/sealskin-isolation/lclgfmnljgacfdpmmmjmfpdelndbbfhk), [Firefox](https://addons.mozilla.org/en-US/firefox/addon/sealskin-isolation/)): the primary client. Context menu entries (open link isolated, open file isolated, send media, search selection), download interception, the launcher popup, the admin UI, and the file manager. +- **Mobile apps** ([iOS](https://apps.apple.com/us/app/sealskin/id6758210210), [Android](https://play.google.com/store/apps/details?id=io.linuxserver.sealskin)): Capacitor apps wrapping the same client code. Sessions open in the system browser for full performance; context menu and download interception are extension only. +- Best experience is on desktop Chromium family browsers. Firefox works with some performance trade offs. + +## Deployment notes + +- The supported deployment is the LinuxServer.io server image from [linuxserver/docker-sealskin](https://github.com/linuxserver/docker-sealskin). The server needs the Docker socket, a `/config` volume, a `/storage` volume for user homes, and a TLS certificate and key for the session proxy. +- Configuration is via `SEALSKIN_*` environment variables (`SEALSKIN_API_PORT`, `SEALSKIN_SESSION_PORT`, `SEALSKIN_APP_RESOURCE_PATH` for the app store URL, `SEALSKIN_PUID` and `SEALSKIN_PGID` for launched containers, storage paths, auto update interval, and more). +- SealSkin expects to own its ports; it does not currently sit behind a traditional reverse proxy, the bundled Caddy is the proxy. +- App images update automatically in the background when enabled, and the app store cache refreshes hourly by default. + +## Privacy + +The clients talk only to the server you configure. No analytics, no tracking, no third parties; the developers have no access to your server or data. Full policy in [PRIVACY.md](https://github.com/selkies-project/sealskin/blob/master/PRIVACY.md). diff --git a/docs/selkies/components/selkies-desktop.md b/docs/selkies/components/selkies-desktop.md new file mode 100644 index 00000000..c8b25949 --- /dev/null +++ b/docs/selkies/components/selkies-desktop.md @@ -0,0 +1,61 @@ +# Selkies Desktop + +**Repository:** [selkies-project/selkies-desktop](https://github.com/selkies-project/selkies-desktop) · **License:** MPL-2.0 · **Enable with:** `-e SELKIES_DESKTOP=true` + +Selkies Desktop is a deliberately tiny desktop shell for single application containers. It is a single C program (one source file, no toolkit, drawing with Cairo over Wayland) that turns a bare labwc session into something that feels like a traditional desktop, without dragging in a full desktop environment. + +## What it provides + +- A categorized **application launcher** (start menu) built from the standard `.desktop` files on the system +- A persistent **bottom panel with a taskbar** for switching, minimizing, and restoring windows +- A **wallpaper layer** with the Selkies icon +- **Desktop icons** mapped from `~/Desktop`, launched with a double click + +## The philosophy + +The upstream README is explicit: this is **not** a general purpose desktop environment. Paths are hardcoded, sizes are fixed, and styling is baked in. The goal is narrow: expand on labwc's default right click menu so a single app container gets light multi window capability, a start menu, and a taskbar, at a cost of one small binary with no dependencies beyond Cairo and wayland-client. + +If you need a real desktop, use a [Webtop](../user-guide/apps.md). Selkies Desktop fills the gap between "one maximized app" and "full KDE Plasma". + +## How to enable it + +In any Selkies baseimage derived container running in Wayland mode: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + -e SELKIES_DESKTOP=true \ + lscr.io/linuxserver/firefox:latest bash +``` + +Wayland mode (`PIXELFLUX_WAYLAND=true`) is a prerequisite, it is the default on the current images. The variable is labwc only: Webtop KDE images ignore it since they ship their own shell. + +## How it works + +The binary is prebuilt into every Selkies baseimage at `/usr/bin/selkies-desktop`. When `SELKIES_DESKTOP=true`, the session launcher starts labwc, waits a moment, then runs `selkies-desktop` as the foreground session process connected to labwc's `wayland-0` socket (with `DISPLAY=:0` for XWayland apps). When it exits the session ends. + +Under the hood it uses two wlroots protocols: + +- **wlr layer shell** for its two surfaces: the panel (bottom anchored, 30px exclusive zone, dynamically raised above windows while the start menu is open) and the background layer (full screen, input passing through everywhere except desktop icon cells). +- **wlr foreign toplevel management** to track open windows for the taskbar: titles, app IDs, icons, minimize and activate actions. + +Other implementation notes worth knowing: + +- Application entries come from scanning `/usr/share/applications` and `~/.local/share/applications`, with freedesktop categories folded into a small fixed set (Internet, Multimedia, Development, Games, Office, System, Utilities, and so on). +- Icons resolve from hicolor, Adwaita, Papirus, and pixmaps, with SVG rasterization handled by a vendored nanosvg, no icon library dependency. +- Apps launch through `dex`, `gio launch`, or `gtk-launch`, whichever exists. +- The panel watches `~/.config/panel-reload` with inotify. Touch that file to force a rescan of applications and desktop icons: + +```bash +touch ~/.config/panel-reload +``` + +## Building from source + +```bash +sudo apt install libcairo2-dev libwayland-dev wayland-protocols curl build-essential +make +``` + +The Makefile generates the Wayland protocol bindings with `wayland-scanner` from vendored XML files (`make fetch-deps` refreshes them). Output is a single `selkies-desktop` binary. The baseimage builds it in a dedicated Docker stage and copies the binary in, a nice reference for how small a stack component can be. diff --git a/docs/selkies/components/selkies.md b/docs/selkies/components/selkies.md new file mode 100644 index 00000000..26689f5a --- /dev/null +++ b/docs/selkies/components/selkies.md @@ -0,0 +1,49 @@ +# Selkies + +**Repository:** [selkies-project/selkies](https://github.com/selkies-project/selkies) · **License:** MPL-2.0 · **Upstream docs:** [selkies-project.github.io/selkies](https://selkies-project.github.io/selkies/) + +Selkies is the heart of the platform: a ground up, web native remote desktop protocol and the Python server plus web client that implement it. It began in the Selkies project founded by Dan Isla (ex Google, ex NASA) with Seungmin Kim, targeting cloud gaming class remote desktops for Linux, and it is developed today in partnership between the Selkies organization and LinuxServer.io. + +## What the server does + +`selkies` is a Python asyncio application (console script `selkies`, installed in the baseimages at `/lsiopy/bin/selkies`) that owns the session: + +- **Video**: drives [pixelflux](pixelflux.md) capture and encoding, and broadcasts encoded frames to all connected clients over the WebSocket, with per client backpressure (frame acknowledgements, RTT smoothing, and stall detection) so one slow viewer does not degrade the rest. +- **Audio out**: drives pcmflux, which captures the PulseAudio `output.monitor` source and Opus encodes at up to 320kbps. +- **Microphone in**: receives PCM from the browser and plays it into a virtual PulseAudio source (`SelkiesVirtualMic`) that session apps consume as a normal mic. +- **Input**: injects keyboard, mouse, touch, and scroll. On Wayland, injection goes through pixelflux's compositor APIs with an xkbcommon keymap (plus [waylandtyper](https://github.com/linuxserver/waylandtyper), our maintained fork of `wtype`, for unicode text batches). On X11, through pynput, xdotool, and python-xlib. Gamepads are handled by per slot Unix socket servers feeding the joystick interposer (below). +- **Clipboard**: bidirectional sync via `wl-clipboard` on Wayland or `xclip` on X11, with optional binary (image) clipboard support and chunked transfer for large payloads. +- **Files**: receives chunked uploads over the socket into the session; downloads are served by the container's Nginx file index. +- **Settings and stats**: pushes the sanitized settings schema to the client (this is what builds the sidebar UI, including which controls are locked), and streams CPU, GPU, memory, and network stats. +- **Sharing and roles**: manages primary, collaborator, view only, and player 2 to 4 roles, either via URL fragments (`#shared`, `#collab`, `#player2`) or, in secure deployments, via a token control plane on an internal port (`POST /tokens` authorized by `SELKIES_MASTER_TOKEN`), which is what [SealSkin](sealskin.md) uses for its collaboration rooms. + +Configuration is uniform: every setting is simultaneously a CLI flag (`--framerate`) and an environment variable (`SELKIES_FRAMERATE`), with the value syntax (ranges, enums, `|locked`) described in the [Configuration Reference](../user-guide/configuration.md). The protocol itself is documented in [The Streaming Protocol](../developer-guide/protocol.md). + +## The web client + +The client lives in the same repository under `addons/`: + +- **selkies-web-core**: the engine. Connects the WebSocket, demultiplexes binary frames, decodes H.264 with WebCodecs `VideoDecoder`, JPEG stripes with `createImageBitmap`, and Opus in a worker, renders to canvas, plays audio through an AudioWorklet, and captures all input including trackpad gestures and pointer lock. It exposes a documented `postMessage` API so any UI can be built on top of it. +- **selkies-dashboard**: the standard React sidebar UI described in [Using the Web Client](../user-guide/web-client.md), fully internationalized. Variants exist (`selkies-dashboard-wish` is a newer TypeScript and shadcn based rewrite), and the container's `DASHBOARD` variable selects between built dashboards. The postMessage split means a white label or kiosk frontend is just another dashboard. +- **universal-touch-gamepad**: an on screen touch gamepad that injects itself into the browser's Gamepad API with configurable layouts. + +## The gamepad trick + +Two small C addons make plug and play gamepads work in unprivileged containers with no kernel devices: + +- **js-interposer**: an `LD_PRELOAD` library that intercepts `open()` calls to `/dev/input/js*` and `/dev/input/event*` and transparently redirects them to Unix sockets served by Selkies, faithfully emulating the Linux joystick and evdev APIs (ioctls included). Apps think they opened a real Xbox 360 pad. +- **fake-udev**: a stub `libudev` that makes device enumeration APIs report the synthetic pads with a plausible sysfs hierarchy, for apps and engines (SDL, browsers) that discover devices through udev rather than by opening device nodes. + +Both are preloaded automatically in the baseimages, and `NO_GAMEPAD=true` turns the whole mechanism off. + +## Ports (inside a container) + +| Port | What | +| --- | --- | +| 8082 | The data WebSocket (`SELKIES_PORT`; upstream default is 8081, the baseimages set 8082), proxied by Nginx at `/websocket` | +| 8083 | Token control plane for secure sharing mode, never expose it | +| 3000 / 3001 | Nginx HTTP and HTTPS in front of everything ([baseimage](baseimages.md) territory) | + +## Relationship to the rest of the stack + +Selkies deliberately does not do: TLS, HTTP auth, process supervision, GPU detection, or app packaging. All of that belongs to the [baseimage](baseimages.md). And it does not do capture or encoding itself, that is [pixelflux and pcmflux](pixelflux.md). This separation is what makes the pieces independently reusable: you can run the Selkies server against your own frontend, or embed pixelflux without any of Selkies. diff --git a/docs/selkies/developer-guide/.pages b/docs/selkies/developer-guide/.pages new file mode 100644 index 00000000..5cda47d3 --- /dev/null +++ b/docs/selkies/developer-guide/.pages @@ -0,0 +1,8 @@ +nav: + - index.md + - Platform Architecture: architecture.md + - Building Custom Images: building-images.md + - Baseimage Internals: baseimage-internals.md + - Customizing Containers: customization.md + - The Streaming Protocol: protocol.md + - Development Environment: development.md diff --git a/docs/selkies/developer-guide/architecture.md b/docs/selkies/developer-guide/architecture.md new file mode 100644 index 00000000..d763a816 --- /dev/null +++ b/docs/selkies/developer-guide/architecture.md @@ -0,0 +1,83 @@ +# Platform Architecture + +This page is the technical deep dive into how a Selkies container actually works, from compositor to canvas. It assumes you have read the high level overview on the [home page](../index.md). + +## Process topology (Wayland mode) + +Everything runs in one container under s6-overlay supervision: + +```mermaid +graph TD + S6[s6-overlay] --> NGINX[nginx :3000/:3001] + S6 --> PULSE[pulseaudio, null sinks output and input] + S6 --> SELKIES[selkies python server :8082 ws] + S6 --> DE[startwm_wayland.sh] + SELKIES -->|in process| PF[pixelflux: Smithay compositor + encoders, socket wayland-1] + DE --> LABWC[labwc, client of wayland-1, serves wayland-0 and XWayland :0] + LABWC --> APP[application from autostart] + SELKIES --> PCM[pcmflux, opus from pulse monitor] +``` + +The subtle point people miss: **pixelflux is the display server.** When the Selkies python process starts in Wayland mode it calls `ensure_wayland_display()`, which spins up a headless Smithay based compositor inside the pixelflux extension, listening on `wayland-1`. There is no separate compositor daemon and no DRM or seat management, the "monitor" is a buffer that pixelflux owns, on GPU or in CPU memory. + +labwc then runs as a *client* of that compositor (using its Wayland backend, the nested pattern) and provides window management, decorations, and XWayland on display `:0` for legacy apps. Applications connect to labwc's `wayland-0` socket. Full desktops swap labwc for a heavier nested compositor: Webtop KDE runs `kwin_wayland` nested on `wayland-1` with plasmashell on top. + +In X11 fallback mode the shape is more traditional: a patched Xvfb (with DRI3 device support) provides `:1`, Openbox manages windows, and pixelflux captures via XSHM with per stripe hashing for damage detection. + +## The video pipeline + +### Capture and damage + +- Wayland: the compositor knows exactly which rectangles changed each frame, damage tracking is free and exact. +- X11: pixelflux hashes each horizontal stripe of the framebuffer per frame (xxh3) and marks changed stripes dirty, with a "damage block" heuristic that stops re hashing regions that are continuously changing. +- Idle screens take a fast path that costs close to zero CPU. + +### Encoding + +Four encoders behind one policy layer: + +| Path | Encoders | Shape | +| --- | --- | --- | +| CPU | x264, JPEG (and optional OpenH264) | Striped: one stripe per core, parallel encode, only dirty stripes sent | +| GPU | NVENC (Nvidia), VA-API (Intel and AMD) | Full frame, zero copy from DMA-BUF when render and encode share a device | + +Quality logic is shared: infinite GOP with on demand IDR frames, CRF rate control with live retuning, and the **paint over** system, after N static frames, resend at high quality (better JPEG quality, or an H.264 burst at lower CRF), cancelled instantly by motion. + +The full encoder and settings detail lives on the [Pixelflux page](../components/pixelflux.md). + +### Transport and presentation + +Encoded frames go to the Selkies server as callback invocations carrying a compact binary header (type, frame id, stripe geometry), and Selkies broadcasts them raw over the WebSocket, the server never re muxes or re packetizes. In the browser, WebCodecs decodes H.264, `createImageBitmap` handles JPEG stripes, and everything composites onto a canvas. Because the client acknowledges frame ids, the server maintains a per client backpressure window: slow clients get frames dropped *before* encode (keeping the H.264 reference chain valid), fast clients are never held back. Wire formats are specified in [The Streaming Protocol](protocol.md). + +## The audio pipeline + +PulseAudio runs with two null sinks: `output` (what apps play into) and `input` (what apps record from). Pcmflux captures `output.monitor` and Opus encodes 48kHz stereo frames natively, which Selkies broadcasts as binary WebSocket messages, decoded in the browser by an AudioDecoder worker feeding an AudioWorklet. The reverse path takes browser mic PCM, writes it into the `input` sink through a virtual source, and applications see a working microphone. + +## Input + +Browser events become terse text messages over the same socket (`kd,`, `m,,,,`, and friends). Injection differs by stack: + +- **Wayland**: injected through pixelflux's compositor seat, keyboard by scancode against a hot swappable xkb keymap, pointer absolute or relative, with unicode text batched through [waylandtyper](https://github.com/linuxserver/waylandtyper) (our maintained fork of `wtype` that fixes many bugs in the old codebase) for IME correctness. Because the compositor is headless and synthetic, external tools like xdotool do not work here, the API is the only door. +- **X11**: pynput, xdotool, and XTEST, with a keysym map and Cyrillic to QWERTY remapping so shortcuts work across layouts. + +Gamepads bypass the display server entirely: Selkies serves the Linux joystick and evdev protocols over Unix sockets, and the `LD_PRELOAD` interposer plus fake udev library make applications believe `/dev/input/js0` is a real Xbox 360 pad. Four slots exist, mappable to remote players. + +## The web layer + +Nginx inside the container is the single front door: it serves the static client (a React dashboard over the `selkies-web-core` engine), proxies `/websocket` to the Selkies server, serves `/files` downloads with fancyindex, optionally enforces basic auth, applies the `SUBFOLDER` prefix, and proxies `/pelorus/` when the agent layer is on. The dashboard and the engine communicate over a documented `postMessage` API, which is the extension point for custom frontends. + +## Sharing and multi user + +One session, many sockets. Every connected client gets the same broadcast frames; roles (primary, collab, view only, player N) gate which input messages are honored. In secure mode (used by SealSkin), access requires per user tokens registered through a control plane endpoint on an internal port, and roles can be re assigned live, this is what powers collaboration rooms with granular permissions. + +## Where the orchestration layer plugs in + +Nothing in the container knows about SealSkin. The orchestration contract is entirely environment variables and volumes at launch: `SUBFOLDER` for path routing, `CUSTOM_USER` and `PASSWORD` for per session auth injected by the proxy, `SELKIES_MASTER_TOKEN` to switch on token mode, `DRINODE` and `DRI_NODE` for GPU placement, and a bind mounted home with an autostart script written before boot. Any scheduler that can do those five things can orchestrate the platform, that is the entire integration surface, and [SealSkin](../components/sealskin.md) is the reference implementation of it. + +## Performance characteristics + +Design consequences worth knowing when you build on this: + +- **Static cost is near zero.** Idle sessions consume almost no CPU or bandwidth, so dense multi session hosts work, the demonstrated benchmark is eight Firefox sessions streaming youtube on an N97 mini PC. +- **Zero copy changes the economics.** With render and encode on one GPU, the CPU cost of a session is input handling and websocket writes; system RAM bandwidth is untouched by pixels. +- **Latency is dominated by the network.** The pipeline adds a frame or two; backpressure trades throughput for freshness per client automatically. diff --git a/docs/selkies/developer-guide/baseimage-internals.md b/docs/selkies/developer-guide/baseimage-internals.md new file mode 100644 index 00000000..f2e06d05 --- /dev/null +++ b/docs/selkies/developer-guide/baseimage-internals.md @@ -0,0 +1,94 @@ +# Baseimage Internals + +What actually happens inside a Selkies container, boot order, services, and the files involved. Reference material for image builders and anyone debugging a container from the inside. + +## Build composition + +The baseimage Dockerfile is a multi stage build assembling, onto a LinuxServer.io distro base (Debian, Ubuntu, Alpine, Fedora, Arch, or Kali): + +- A patched **Xvfb** (adds a `-vfbdevice` flag for DRI3 GPU passthrough) from the LSIO xvfb image +- The **web frontend**: the Selkies repository pinned to an exact commit, with `selkies-web-core` and the dashboards built and dropped in `/usr/share/selkies/` +- **labwc 0.9.7** built from source with the IPC patch (adds `labwc -i`, a read only JSON window query socket at `$XDG_RUNTIME_DIR/labwc.sock`, consumed by Pelorus) +- A rebuilt **wlroots** with a defensive patch that catches SIGSEGV and SIGBUS inside pixman draw calls and skips the frame instead of crashing the compositor +- **selkies-desktop** and **[waylandtyper](https://github.com/linuxserver/waylandtyper)** (our maintained fork of wtype, fixing many bugs in the old codebase) built from source +- The **selkies** Python package installed into the `/lsiopy` virtualenv (pulling in pixelflux and pcmflux wheels), plus **pelorus** +- The **joystick interposer** (`/usr/lib/selkies_joystick_interposer.so`) and **fake udev** (`/opt/lib/libudev.so.1.0.0-fake`) compiled from the Selkies addons +- Nginx with fancyindex, PulseAudio, mesa and VA-API userspace, Vulkan loaders, all system locales, proot-apps, Docker in Docker machinery, and passwordless sudo for `abc` + +Baked ENV defaults worth knowing: `HOME=/config`, `DISPLAY=:1`, `TITLE=Selkies`, `SELKIES_ENCODER="x264enc,jpeg"`, `START_DOCKER=true`, `DISABLE_ZINK=false`, `DISABLE_DRI3=false`, `NVIDIA_DRIVER_CAPABILITIES=all`, and the interposer path in `SELKIES_INTERPOSER`. + +## Boot: the init chain + +The Selkies oneshots hook into the standard LSIO s6 chain right after `init-os-end` (so PUID and PGID remapping, Docker mods, and custom files all run in their usual order around them): + +``` +init-os-end + └─ init-selkies (marker) + └─ init-nginx + └─ init-selkies-config + └─ init-video + └─ init-selkies-end → init-config → ... → init-services +``` + +**`init-nginx`** renders `/defaults/default.conf` into the live Nginx config with plain string substitution: ports (`CUSTOM_PORT`, `CUSTOM_HTTPS_PORT`, `CUSTOM_WS_PORT`), `SUBFOLDER` tokens, the download path, basic auth (generates `.htpasswd` and uncomments the auth lines when `PASSWORD` is set), IPv6 removal, dev mode rewiring, dashboard selection into `/usr/share/selkies/web`, and the PWA `manifest.json` with your `TITLE`. On first run it also generates the ten year self signed certificate into `/config/ssl/`. + +**`init-selkies-config`** is the big one: + +- Chooses the mode: `PIXELFLUX_WAYLAND=true` selects labwc paths (`$HOME/.config/labwc`, `/defaults/autostart_wayland`, `/defaults/menu_wayland.xml`) and forces `SELKIES_SECOND_SCREEN=false`; otherwise Openbox paths. +- First run copies of `autostart` and `menu.xml` into the config dir (persistent, user editable); `rc.xml` for labwc is regenerated from the template every boot. +- Recreates `$HOME/.XDG` as `XDG_RUNTIME_DIR` and clears stale PulseAudio state, so unclean shutdowns recover. +- Applies every hardening variable (permission stripping, sudoers corruption, menu and keybind editing, locking rc.xml and autostart when watchdog mode is on), the details are in the [Security guide](../user-guide/security.md). +- GPU env: with exactly one render node and nothing set, points `DRINODE` and `DRI_NODE` at it; wires `PIXELFLUX_CU=5000` and `ROOT_PATH=/pelorus` when `PELORUS=true`. +- Creates the gamepad device nodes (`/dev/input/js0-3` and event nodes) and sets the global `LD_PRELOAD` for the interposer and fake udev, unless `NO_GAMEPAD` is set. +- Syncs proot-apps into the user home and handles `LC_ALL` locale derivation. + +**`init-video`** fixes `/dev/dri` and `/dev/dvb` group permissions for `abc` (creating a matching group for the device GID when needed), auto enables `AUTO_GPU` on x86_64 when a render node exists and nothing was configured, probes whether older Intel hardware needs the `i965` VA-API driver, and repairs Nvidia ICD, Vulkan, EGL, and GBM plumbing inside the container. + +## The services + +| Service | Behavior | +| --- | --- | +| `svc-nginx` | Reaps zombie workers then runs Nginx in the foreground | +| `svc-pulseaudio` | PulseAudio as `abc`, never idle exits, runtime dir under `/defaults` | +| `svc-xorg` | X11 mode: Xvfb on `:1` with a max resolution from `MAX_RES` and the DRI3 device flag. Wayland mode: sleeps | +| `svc-selkies` | Loads the `output` and `input` null sinks once PulseAudio is up, then runs `selkies --addr=localhost --mode=websockets` as `abc`. In Wayland mode this is what brings up the pixelflux compositor on `wayland-1` | +| `svc-de` | Waits for the display (the `wayland-1` socket, or `xset q` on X11), then executes `/defaults/startwm_wayland.sh` or `/defaults/startwm.sh` as `abc`, recording the PID for clean teardown. On X11 it also sets the initial 1024x768 mode (or `SELKIES_MANUAL_WIDTH` and `HEIGHT`) via xrandr | +| `svc-xsettingsd` | X11 only, DPI hinting for legacy toolkits | +| `svc-watchdog` | Only active with `RESTART_APP=true`: polls for the autostart process and relaunches it if it dies | +| `svc-dbus` | System D-Bus (present on distros that need it) | +| `svc-docker` | Detects privileged mode and starts the inner Docker daemon unless `START_DOCKER=false` | + +Teardown is equally deliberate: `svc-de`'s finish script walks the session's process tree with a graceful TERM and a five second deadline, so recreating containers does not leave half dead sessions. + +## Nginx layout + +One config, two identical server blocks (HTTP 3000, HTTPS 3001): + +| Location | Purpose | +| --- | --- | +| `SUBFOLDER` (default `/`) | The web client static files from `/usr/share/selkies/web/` | +| `SUBFOLDERwebsocket` | Proxy to the Selkies data WebSocket on 127.0.0.1:8082 | +| `SUBFOLDERfiles` | fancyindex download listing of `FILE_MANAGER_PATH` (default `/config/Desktop`), removed entirely when downloads are disabled or `HARDEN_DESKTOP` is on | +| `SUBFOLDERpelorus/` | Proxy to the Pelorus API on 127.0.0.1:5100 | +| `/devmode` | Proxy to a Vite dev server, see [Development Environment](development.md) | + +All proxy locations use hour long timeouts, no buffering, and a 10MB body cap. Because substitution is plain `sed`, exotic characters in `PASSWORD` or `SUBFOLDER` can break the config, keep them simple. + +## Internal port map + +| Port | Owner | Exposure | +| --- | --- | --- | +| 3000, 3001 | Nginx | Published, everything user facing | +| 8082 | Selkies WebSocket | localhost only, via Nginx | +| 8083 | Selkies token control plane | localhost only, orchestrators call it, never expose | +| 5100 | Pelorus API | localhost only, via Nginx at `/pelorus/` | +| 5000 | pixelflux Computer Use API | localhost only | +| 5173 | Vite dev server | localhost only, via `/devmode` | + +## Wayland session wiring + +`/defaults/startwm_wayland.sh` in the baseimage handles four combinations of `PELORUS` and `SELKIES_DESKTOP`: plain labwc; labwc plus the selkies-desktop panel; labwc with IPC plus the Pelorus and AT-SPI stack; or all of it together, in each case exporting the cursor theme, `us` XKB defaults, and `WAYLAND_DISPLAY=wayland-1` for labwc while the shell and apps land on `wayland-0` with `DISPLAY=:0` for XWayland. Downstream desktops override this file wholesale, see [Building Custom Images](building-images.md#case-study-docker-webtop-ubuntu-kde-full-desktop). + +## Things inherited from the LSIO base + +Not implemented here, but always available: `PUID`, `PGID`, `TZ`, `UMASK`, `DOCKER_MODS`, `/custom-cont-init.d`, and `/custom-services.d`. Those hooks are the subject of [Customizing Containers](customization.md). diff --git a/docs/selkies/developer-guide/building-images.md b/docs/selkies/developer-guide/building-images.md new file mode 100644 index 00000000..be568902 --- /dev/null +++ b/docs/selkies/developer-guide/building-images.md @@ -0,0 +1,193 @@ +# Building Custom Images + +Packaging your own application or desktop on the Selkies baseimage is deliberately boring. This page goes from the minimal case to the two production patterns, a single app (Chromium) and a full desktop (Webtop KDE). + +## The minimal single app image + +```dockerfile +FROM ghcr.io/linuxserver/baseimage-selkies:debiantrixie + +ENV TITLE="My App" +ENV PIXELFLUX_WAYLAND=true + +RUN apt-get update && \ + apt-get install -y --no-install-recommends my-app && \ + apt-get autoclean && \ + rm -rf /var/lib/apt/lists/* /var/tmp/* /tmp/* + +COPY /root / + +EXPOSE 3001 +VOLUME /config +``` + +with a repository layout of: + +``` +root/ +└── defaults/ + ├── autostart_wayland # what to run in Wayland mode + └── autostart # what to run in X11 fallback mode +``` + +and `root/defaults/autostart_wayland` containing, in the simplest case, one line: + +```bash +#!/bin/bash +my-app +``` + +Build it, run it with `--shm-size=1gb -p 3001:3001`, and your app is streaming. Everything else on this page is refinement. + +## The downstream contract + +The baseimage looks for these files. All are optional, sane defaults exist for each: + +| File | Role | +| --- | --- | +| `/defaults/autostart_wayland` | Command launched inside labwc (Wayland). Runs as the `abc` user. Copied to `/config/.config/labwc/autostart` on first run only, so users can edit it and their edits persist | +| `/defaults/autostart` | Same for Openbox (X11 fallback) | +| `/defaults/menu_wayland.xml`, `/defaults/menu.xml` | Right click root menu (Openbox menu XML schema, shared by labwc) | +| `/defaults/startwm_wayland.sh`, `/defaults/startwm.sh` | Replace the *entire* session startup. This is the full desktop hook, override these and you own the session | +| `/usr/share/selkies/www/icon.png` | App icon: favicon, PWA icon, and the Selkies Desktop wallpaper mark | + +Also set `ENV TITLE` (browser tab title) and keep `VOLUME /config`. + +!!! tip "First run copy semantics" + Autostart and menu files are copied into `/config` only if they do not already exist there. That makes them user editable and persistent, but it also means testing changes to them requires a fresh `/config` volume, a classic gotcha. + +## Case study: docker-chromium (single app) + +The [Chromium image](https://github.com/linuxserver/docker-chromium) is the canonical single app pattern. Its Dockerfile is four steps: replace the icon, `apt-get install chromium chromium-l10n`, clean up, `COPY /root /`. The interesting engineering is in the overlay: + +**A wrapper script**, `/usr/bin/wrapped-chromium`, instead of launching the browser directly: + +```bash +#!/bin/bash +# Cleanup stale singleton locks so a recreated container does not +# hit the "profile in use" dialog +if ! pgrep chromium > /dev/null; then + rm -f $HOME/.config/chromium/Singleton* +fi + +# Use native Wayland when a GPU is present and labwc is running +if ls -l /dev/dri/* > /dev/null 2>&1 && pgrep labwc > /dev/null; then + WAYLAND="--ozone-platform=wayland" +fi + +# Accessibility for the Pelorus agent layer +if [ "${PELORUS,,}" == "true" ]; then + ACCESSIBILITY="--force-renderer-accessibility" +fi + +exec /usr/bin/chromium \ + --no-sandbox \ + --test-type \ + --password-store=basic \ + --start-maximized \ + ${WAYLAND} ${ACCESSIBILITY} "$@" +``` + +Notes on those flags: `--no-sandbox` is required because the container itself is the sandbox boundary and Chromium's own sandbox needs privileges the container deliberately lacks; `--password-store=basic` avoids keyring prompts; a symlink at `/usr/bin/chromium-browser` catches anything invoking the standard name. + +**The autostart passes user flags through an env var:** + +```bash +#!/bin/bash +wrapped-chromium ${CHROME_CLI} +``` + +That `${CHROME_CLI}` convention (each browser image has its equivalent, `FIREFOX_CLI`, `VIVALDI_CLI`, and so on) is how end users add flags or a start URL without rebuilding, document your own equivalent if your app takes arguments. + +**A menu.xml** offering the app and a terminal (`foot` on Wayland, `xterm` on X11) on right click, so the session is recoverable if the app is closed. + +```bash + + + +/usr/bin/foot +/usr/bin/wrapped-chromium --enable-features=UseOzonePlatform --ozone-platform=wayland + + +``` + +## Case study: docker-webtop ubuntu-kde (full desktop) + +The [Webtop KDE image](https://github.com/linuxserver/docker-webtop/tree/ubuntu-kde) shows the desktop pattern. Instead of autostart files (its `/defaults/autostart` is literally `exit 0`), it overrides the session scripts: + +- **`/defaults/startwm_wayland.sh`** owns the whole session: it seeds KDE config on first run (disable compositing, disable the lock screen), exports the KDE session environment (`QT_QPA_PLATFORM=wayland`, `XDG_CURRENT_DESKTOP=KDE`), then inside a dbus session starts `kwin_wayland` **nested on the pixelflux socket** (`WAYLAND_DISPLAY=wayland-1`) with XWayland, waits, starts the polkit agent, and runs `plasmashell` on `wayland-0` as the foreground process. A small Python shim pre binds the X11 socket so XWayland lands on a predictable display number. +- **`/defaults/startwm.sh`** (the X11 path) is a deliberate stub that displays an unsupported platform message, this flavor is Wayland only. +- It bridges the platform's autostart convention into KDE by dropping a `.desktop` file in `~/.config/autostart` that executes the user's persistent autostart script, so features like SealSkin's autostart injection work identically on desktops. +- Build steps worth stealing: `setcap -r /usr/bin/kwin_wayland` (strip file capabilities so KWin runs unprivileged in a container), rewriting the Chromium `.desktop` Exec to the wrapper script, and swapping in `wl-clipboard-rs` for clipboard tooling. + +The other Webtop flavors are lighter: Alpine XFCE's entire session script is essentially `WAYLAND_DISPLAY=wayland-1 startxfce4 --wayland`, some desktops can talk to the pixelflux compositor directly without a nested compositor. + +## The window manager layer: labwc and Openbox + +Single application containers run [labwc](https://github.com/labwc/labwc) on Wayland, which is a **1:1 replacement for Openbox**: it consumes the same menu XML schema and an rc.xml with the same concepts, so everything below applies to both stacks, with the X11 fallback simply using Openbox itself. Full desktop Webtops (KDE, XFCE, and friends) bring their own window management and ignore this layer entirely. + +### The right click menu: menu.xml + +The root menu users get by right clicking the desktop is plain Openbox menu XML. The baseimage default (`/defaults/menu_wayland.xml` for labwc, `/defaults/menu.xml` for Openbox) is just a terminal entry: + +```xml + + + + + /usr/bin/foot + + + +``` + +Downstream images override these files to add their application, so the session is recoverable if the app is closed. Like the autostart scripts, menu files are copied to `/config/.config/labwc/` (or `.../openbox/`) on first run only, after which the user's copy wins. + +### Our rc.xml defaults + +The labwc config ships as `/defaults/labwc.xml` and is copied to `/config/.config/labwc/rc.xml` on first run (Openbox uses the system `/etc/xdg/openbox/rc.xml`, regenerated from a backup at each start). The notable defaults, identical in intent on both platforms: + +- **Server side decorations** with a titlebar layout of `icon:iconify,max,close`, and a **window rule maximizing every window** on launch so the app fills the stream. +- **Per app decoration overrides**: browsers that draw their own tab bar (Chromium, Firefox, Brave, Vivaldi, and the rest) get `serverDecoration="no"` rules so they render edge to edge. +- **Mouse bindings on the desktop**: right click shows the root menu, middle click shows the combined client list (how users restore minimized windows). +- **Keybinds**: `alt+f4` and `alt+escape` close windows, `alt+space` opens the window menu, and Super+E launches a terminal. + +### How the hardening variables rewrite it + +At container init, `init-selkies-config` rewrites the rc.xml based on environment variables, which is exactly how the [hardening family](../user-guide/security.md#hardening-variables) works under the hood: + +| Variable | rc.xml edit | +| --- | --- | +| `NO_DECOR` | Flips the decoration rules to `serverDecoration="no"` (Openbox: injects `no`) | +| `NO_FULL` | Deletes the maximize on launch window rule | +| `DISABLE_CLOSE_BUTTON` | Strips `close` from the titlebar button layout | +| `DISABLE_MOUSE_BUTTONS` | Deletes the right and middle click mousebinds, removing the root menu and client list | +| `HARDEN_KEYBINDS` | Comments out the escape hatch keybinds (`alt+f4`, `alt+escape`, `alt+space`, Super+E) | + +When `DISABLE_MOUSE_BUTTONS` or `HARDEN_KEYBINDS` is active, the resulting rc.xml is chowned to root and made read only (mode 444) so the session user cannot undo the lockdown from inside. + +**Scope**: because all of this operates on labwc and Openbox configuration, the window manager hardening only affects **single application containers**. On full desktop Webtops these variables have nothing to rewrite; the desktop environment's own policies apply there. + +## Hardening and kiosk builds + +You rarely need image changes for lockdown, the [hardening variables](../user-guide/security.md#hardening-variables) (`HARDEN_DESKTOP`, `HARDEN_OPENBOX`, `RESTART_APP`, and the `SELKIES_*|locked` syntax) do it at runtime through the rc.xml rewrites above plus binary permission changes, which keeps one image serving both open and kiosk deployments. Bake `ENV` defaults into your Dockerfile if you want them locked by default. + +## Testing your image + +```bash +docker build -t my-selkies-app . +docker run --rm -it --shm-size=1gb -p 3001:3001 -v /tmp/testconfig:/config my-selkies-app bash +``` + +Checklist: + +- App launches maximized and undecorated (or as intended) in the browser +- Audio plays, clipboard syncs both ways +- Kill the app inside the session: can you relaunch from the right click menu? Does `RESTART_APP=true` bring it back? +- Recreate the container against the same `/config`: settings persist, no first run dialogs +- Try `SELKIES_DESKTOP=true` and `PELORUS=true` if you intend to support them +- Run the X11 fallback (`-e PIXELFLUX_WAYLAND=false`) unless you are deliberately Wayland only, in which case ship the explanatory stub `startwm.sh` + +## Multi arch and CI + +LinuxServer images ship x86_64 and aarch64 from near identical Dockerfiles (`Dockerfile` and `Dockerfile.aarch64` differing only in the FROM tag). If you publish your own, the same pattern with `docker buildx` covers both. For distribution through SealSkin, add your image to a custom [apps registry](../components/sealskin-apps.md) with an autostart script and it becomes installable in any SealSkin deployment. diff --git a/docs/selkies/developer-guide/customization.md b/docs/selkies/developer-guide/customization.md new file mode 100644 index 00000000..03dfdafe --- /dev/null +++ b/docs/selkies/developer-guide/customization.md @@ -0,0 +1,59 @@ +# Customizing Containers + +You can go remarkably far customizing a stock container at runtime before a custom image is warranted. These are the layers, from lightest to heaviest. + +## 1. Environment variables + +The first stop, and often the last. The [Configuration Reference](../user-guide/configuration.md) covers stream tuning, UI visibility, feature gating, and the `|locked` syntax; the [Security page](../user-guide/security.md) covers the hardening family. Between `TITLE`, `DASHBOARD`, watermarks, UI toggles, and locks, most "white label kiosk" requirements are pure configuration. + +## 2. The persistent config volume + +Because `/config` is the user home, a lot of behavior is just files you can manage there: + +- **`/config/.config/labwc/autostart`** (or `.../openbox/autostart` in X11 mode): the session launch script. The image default is copied here on first run and never overwritten, so you can edit what starts, add companion processes, or export app specific environment. Note that with `RESTART_APP=true` this file is locked read only by design. +- **Menus** (`menu.xml`) live alongside and follow the same first run copy rule, and **`rc.xml`** (window manager behavior, keybinds, decorations) lives there too. The schema and our default edits are documented in [the window manager layer](building-images.md#the-window-manager-layer-labwc-and-openbox). +- Application dotfiles, browser policies, desktop settings: seed them into the volume before first boot and the session starts preconfigured. This is exactly how SealSkin's template and App Lab features work. + +## 3. Custom scripts and services (LSIO hooks) + +Inherited from the LinuxServer base and fully supported here: + +- **`/custom-cont-init.d/`**: mount a directory of scripts to run once at container init, as root, before services start. Ideal for installing a package, dropping a config file, or patching something in the image layer. + +```yaml + volumes: + - ./my-init:/custom-cont-init.d:ro +``` + +- **`/custom-services.d/`**: mount scripts to run as supervised long running services alongside the stack, a sidecar daemon inside the session container. + +## 4. Docker mods + +Reusable, shareable customization layers applied at startup via the `DOCKER_MODS` variable, multiple mods separated by `|`. The most useful with these images: + +```yaml + environment: + - DOCKER_MODS=linuxserver/mods:universal-package-install + - INSTALL_PACKAGES=fonts-noto-cjk|mpv +``` + +The [mod catalog](https://mods.linuxserver.io/) has many more, and writing your own mod is a tiny Dockerfile that layers files into the container at start, effectively a runtime `COPY /root /`. + +## 5. proot-apps + +Persistent, per user application installs into `/config` that survive image updates, covered in [Installing Applications](../user-guide/installing-apps.md). Relevant to developers because it is scriptable: a custom init script can `proot-apps install` a set of tools on first boot to build a standard environment without owning an image. + +## 6. Custom dashboards and frontends + +The web client separates the streaming engine (`selkies-web-core`) from the UI (the dashboard) with a documented `postMessage` API: the engine handles the socket, decode, render, and input, and emits status events; the dashboard sends control messages (`settings`, `pipelineControl`, `setManualResolution`, clipboard updates, and so on). Ship your own dashboard and select it with the `DASHBOARD` variable, or in a custom image drop your build into `/usr/share/selkies/` alongside the stock ones. See the selkies-web-core README in the [Selkies repository](https://github.com/selkies-project/selkies) for the full message catalog, and [Development Environment](development.md) for live reload while building one. + +## When to graduate to a custom image + +Rules of thumb: + +- Packages needed at boot every time, and start time matters → custom image +- The same customization on more than a couple of deployments → custom image +- Anything touching the session scripts (`startwm*`) → custom image +- One off tweak, user level config, or an experiment → stay with the runtime layers above + +The jump is small by design, take your working `custom-cont-init.d` script and it usually translates line for line into a Dockerfile `RUN`, plus a `COPY /root /` for your files. Continue at [Building Custom Images](building-images.md). diff --git a/docs/selkies/developer-guide/development.md b/docs/selkies/developer-guide/development.md new file mode 100644 index 00000000..3d50740e --- /dev/null +++ b/docs/selkies/developer-guide/development.md @@ -0,0 +1,72 @@ +# Development Environment + +How to hack on the stack itself. The short version: you do not build any of this on your desktop. The container is the development environment, you mount your source into it and the init system runs it live with hot reload. + +## Dev mode in the containers + +Every Selkies baseimage container ships a development mode designed for iterating on the Selkies server, web client, and pixelflux, using the container as your runtime so you never have to reproduce the compositor and audio stack locally. + +### Selkies server and web client + +Clone the [selkies repository](https://github.com/selkies-project/selkies), mount it at `/config/src`, and set `DEV_MODE` to the dashboard you want served: + +```bash +git clone https://github.com/selkies-project/selkies.git +cd selkies +git checkout -f lsio +docker run --rm -it \ + --shm-size=1gb \ + -e DEV_MODE=selkies-dashboard \ + -e PUID=1000 \ + -e PGID=1000 \ + -v $(pwd):/config/src \ + -p 3001:3001 ghcr.io/linuxserver/webtop:ubuntu-kde bash +``` + +The application restarts on code changes to the mounted source directory and provides feedback for debugging. The web side runs through Vite with hot module reload, and Nginx swaps the dev server to the root path so `https://localhost:3001/` serves your live build. + +`DEV_MODE` values: + +| Value | Behavior | +| --- | --- | +| `selkies-dashboard` (or another dashboard directory name) | Runs the dashboard through Vite with hot module reload, watching your mounted source, with the Python server under a watcher as well | +| `core` | Watches and rebuilds `selkies-web-core`, the streaming engine itself | +| `pixelflux` | Rapid development against a mounted pixelflux checkout, see below | + +### Pixelflux + +The same pattern gives you a pixelflux rapid development environment. Mount a pixelflux checkout instead and set `DEV_MODE=pixelflux`: + +```bash +git clone https://github.com/linuxserver/pixelflux.git +cd pixelflux +docker run --rm -it \ + --shm-size=1gb \ + -e DEV_MODE=pixelflux \ + -e PUID=1000 \ + -e PGID=1000 \ + -v $(pwd):/config/src \ + -p 3001:3001 ghcr.io/linuxserver/webtop:ubuntu-kde bash +``` + +The container builds the mounted source (all Rust build dependencies are present in the image) and runs the session against your working copy, so a compositor or encoder change is one container restart away from being on screen. For pipeline experiments outside of Selkies entirely, `example/screen_to_browser.py` in the pixelflux repository is a complete standalone streaming server against `example/index.html`, the fastest possible inner loop. + +## Repository map + +| Repository | What you touch there | +| --- | --- | +| [selkies-project/selkies](https://github.com/selkies-project/selkies) | Server, protocol, web client, dashboards, gamepad addons | +| [linuxserver/pixelflux](https://github.com/linuxserver/pixelflux) | Capture, compositor, encoders, Computer Use API | +| [linuxserver/docker-baseimage-selkies](https://github.com/linuxserver/docker-baseimage-selkies) | Packaging, init scripts, Nginx, hardening, patches. One branch per distro | +| [linuxserver/docker-webtop](https://github.com/linuxserver/docker-webtop) and the app repositories | Downstream images, one branch per flavor for Webtop | +| [selkies-project/sealskin](https://github.com/selkies-project/sealskin) | Orchestration server, extension, mobile | +| [linuxserver/sealskin-apps](https://github.com/linuxserver/sealskin-apps) | App manifests and autostart scripts | +| [linuxserver/pelorus](https://github.com/linuxserver/pelorus) | Agent API and chat UI | +| [selkies-project/selkies-desktop](https://github.com/selkies-project/selkies-desktop) | The minimal desktop shell | +| [linuxserver/waylandtyper](https://github.com/linuxserver/waylandtyper) | Our maintained fork of wtype for unicode text injection | + +The baseimage Dockerfile is the authoritative recipe for everything compiled into the images (labwc with the IPC patch, the patched wlroots, selkies-desktop, waylandtyper), so when you need to know exactly how a piece is built, read the corresponding Dockerfile stage. + +## Contributing + +Development happens in the open across all of the above. Bug reports with the [minimal command](../user-guide/troubleshooting.md#step-zero-the-minimal-command) reproduction are gold. For discussion, the [LinuxServer.io Discord](https://linuxserver.io/discord) and the Selkies project community are the places to be. Licensing: the Python server, pixelflux, and selkies-desktop are MPL-2.0; the containers are GPL-3. diff --git a/docs/selkies/developer-guide/index.md b/docs/selkies/developer-guide/index.md new file mode 100644 index 00000000..b73dffc3 --- /dev/null +++ b/docs/selkies/developer-guide/index.md @@ -0,0 +1,20 @@ +# Developer Guide + +This guide is for people who build on the platform rather than just run it: packaging your own application or desktop as a Selkies container, customizing the runtime, integrating the underlying libraries, or contributing to the stack itself. + +## Which page do you need? + +- **"I want to ship my app in a browser."** Read [Building Custom Images](building-images.md). It is a Dockerfile of a dozen lines, and the page dissects two real production examples (Chromium and Webtop KDE). +- **"I need to understand how it all fits together first."** [Platform Architecture](architecture.md) is the technical deep dive: compositors, encoding paths, transport, and process supervision. +- **"I need to know what happens inside the container at boot."** [Baseimage Internals](baseimage-internals.md) documents the s6 service tree, the init scripts, Nginx, and every file in the downstream contract. +- **"I want to tweak a stock container without building an image."** [Customizing Containers](customization.md): Docker mods, custom scripts, custom services, and the persistent config surface. +- **"I am implementing a client or debugging the wire."** [The Streaming Protocol](protocol.md) documents the WebSocket message formats, binary frame headers, and the settings handshake. +- **"I want to hack on Selkies, pixelflux, or the dashboards."** [Development Environment](development.md) covers dev mode, live reload, and building each component from source. + +## The mental model + +Three ideas carry the entire developer story: + +1. **The baseimage does everything generic.** TLS, auth, supervision, GPU detection, audio, input, the client. Your image adds packages and, at minimum, one line in an autostart file. Resist the urge to re solve problems the base already solves. +2. **Configuration is environment variables all the way down.** The same `SELKIES_*` settings that users tweak are your deployment API, including the `|locked` mechanism that turns a general purpose image into a locked kiosk without rebuilding. +3. **Session persistence is exactly `/config`.** Design your image so state the user cares about lands in the home directory, and image updates become free. diff --git a/docs/selkies/developer-guide/protocol.md b/docs/selkies/developer-guide/protocol.md new file mode 100644 index 00000000..8b93bf36 --- /dev/null +++ b/docs/selkies/developer-guide/protocol.md @@ -0,0 +1,91 @@ +# The Streaming Protocol + +The wire protocol between the Selkies server and the web client, for anyone implementing a client, embedding the engine, or debugging with a network inspector. Everything rides **one WebSocket** (proxied at `/websocket`), carrying a mix of binary frames and terse text messages. There is no negotiation dance: connect, receive settings, start receiving media. + +## Connection and roles + +The client connects to `wss://host/websocket`. Role assignment happens one of two ways: + +- **Fragment mode** (default): the URL fragment the page was opened with decides the role, `#shared` (view only), `#collab` (full control), `#player2` through `#player4` (gamepad slot only), `#display2-right` and friends (second monitor surface). No fragment means primary. +- **Token mode** (when the server was started with `SELKIES_MASTER_TOKEN`): the client must present `?token=` in the WebSocket URL. Tokens and their roles are registered by the orchestrator via `POST /tokens` on the internal control port with the master token as a bearer credential. Close codes: `4001` invalid token, `4002` revoked, `4029` reconnecting too fast. + +On success the server sends `MODE websockets`, an auth confirmation with the assigned role, and a `server_settings` JSON message containing every tunable setting with its value, allowed range or enum, and locked flag, this single message is what renders the sidebar UI, which is why locking a setting server side removes the control everywhere. + +## Binary messages, server to client + +The first byte of every binary frame is a type tag: + +| Tag | Payload | Header layout (big endian) | +| --- | --- | --- | +| `0x00` | Full frame H.264 | `[1]` keyframe flag, `[2:4]` uint16 frame id, payload follows | +| `0x01` | Opus audio packet | 2 byte header, then the Opus packet | +| `0x03` | JPEG stripe | `[2:4]` frame id, `[4:6]` stripe Y offset, then the JPEG | +| `0x04` | H.264 stripe | `[1]` frame type (1 IDR, 2 I, 0 other), `[2:4]` frame id, `[4:6]` stripe Y offset, `[6:8]` width, `[8:10]` height, then Annex B NALs | + +These headers are produced by pixelflux itself (see the [wire format details](../components/pixelflux.md#api-sketch)); the Python server broadcasts them untouched. A full frame is just a stripe at Y offset 0 with full height. The client feeds H.264 to a WebCodecs `VideoDecoder` per stream, decodes JPEG stripes with `createImageBitmap`, and composites stripes onto the canvas at their Y offsets. The frame type byte reflects what the encoder actually emitted, clients use it to recover decoder state after drops. + +## Binary messages, client to server + +| Tag | Meaning | +| --- | --- | +| `0x01` | File upload chunk (between `FILE_UPLOAD_START` and `FILE_UPLOAD_END` text messages) | +| `0x02` | Microphone PCM, s16le mono 24kHz | + +## Text messages, client to server + +Compact comma separated commands. The important families: + +| Command | Parameters | Description | +| ------------------------------------ | ------------------------- | --------------------------------------------------------------------------- | +| `kd,` | `keysym` | Key down event | +| `ku,` | `keysym` | Key up event | +| `kr` | — | Reset keyboard modifiers | +| `m,,,,` | `x`, `y`, `mask`, `mag` | Absolute pointer event with button mask and scroll magnitude | +| `m2,,,,` | `dx`, `dy`, `mask`, `mag` | Relative pointer event (trackpad and pointer lock modes) | +| `r,,` | `WxH`, `displayId` | Resize request | +| `s,` | `dpi` | Set display DPI | +| `js,c\|d\|b\|a,...` | Event-specific | Gamepad connect (`c`), disconnect (`d`), button (`b`), or axis (`a`) events | +| `cw,` | `b64` | Clipboard write (text) | +| `cb,,` | `mime`, `b64` | Binary clipboard write | +| `cws` / `cbs` | — | Chunked clipboard transfer start (text / binary) | +| `cwd` / `cbd` | Chunk data | Chunked clipboard transfer data (text / binary) | +| `cwe` / `cbe` | — | Chunked clipboard transfer end (text / binary) | +| `co,end,` | `text` | Commit composed text (IME) | +| `SETTINGS,` | `json` | Change stream settings (validated and clamped server-side) | +| `CLIENT_FRAME_ACK ` | `id` | Backpressure acknowledgement | +| `START_VIDEO` | — | Start video stream | +| `STOP_VIDEO` | — | Stop video stream | +| `START_AUDIO` | — | Start audio stream | +| `STOP_AUDIO` | — | Stop audio stream | +| `FILE_UPLOAD_START::` | `relpath`, `size` | Begin file upload | +| `FILE_UPLOAD_END:` | `path` | Complete file upload | +| `cmd,` | `shell` | Run a command in the session (gated by `SELKIES_COMMAND_ENABLED`) | +| `_f,` | `fps` | Client FPS telemetry | +| `_l,` | `ms` | Client latency telemetry | + +Every input bearing message is filtered by role: viewers get settings and video control only, players get their `js` slot, `mk_control` grants of mouse and keyboard can be toggled live in token mode. + +## Text and JSON messages, server to client + +- `cursor,{...}`: PNG cursor images with hotspots, delivered out of band so the canvas cursor is pixel perfect without burning it into the video. +- `clipboard,` and the chunked variants: server clipboard changes. +- `system_stats`, `gpu_stats`, `network_stats` JSON blobs every few seconds for the Stats UI. +- `stream_resolution` and `display_config_update` for resize and multi monitor layout changes. +- `VIDEO_STARTED`, `AUDIO_STOPPED`, `PIPELINE_RESETTING ` lifecycle notices, and `system,{"action":"reload"}` when the client should reconnect fresh. + +## Backpressure + +The server stamps every video frame with a wrapping uint16 id; the client periodically acknowledges the last id it presented. The server computes each client's desync, subtracts an RTT allowance (smoothed over recent samples), and if a client falls more than the allowed window behind, frames are withheld for that client *before* encode where possible, keeping H.264 reference chains valid. A client stalled beyond a timeout is disconnected. The intended behavior: one person on hotel wifi does not blur the session for four people on a LAN. + +## Settings flow + +Client sends `SETTINGS,{"framerate":60,"h264_crf":20,...}`; the server validates each key against its schema (range clamp, enum membership, locked flag) and applies what survives, live where pixelflux supports it (quality, framerate, paint over) or with a pipeline restart where it does not (encoder, color mode). The authoritative state then flows back in `server_settings` so all connected clients converge. + +## Implementing a client: a minimal path + +1. Open the WebSocket with `binaryType = 'arraybuffer'`, send `START_VIDEO` and `START_AUDIO` after receiving `server_settings`. +2. Demux on byte zero. Feed `0x00` and `0x04` frames (strip the header, respect the keyframe flag) into a WebCodecs H.264 decoder configured from the stripe dimensions; paint `0x03` JPEGs at their offsets; queue `0x01` Opus into an audio decoder. +3. Send `CLIENT_FRAME_ACK` with the latest presented frame id a few times per second. +4. Map your input events to the `kd`, `ku`, `m` or `m2` grammar. + +That is a functioning viewer; everything else (clipboard, files, gamepads, stats) is additive. Start from the example client in the [pixelflux repository](https://github.com/linuxserver/pixelflux): `example/screen_to_browser.py` plus `example/index.html` are a complete working server and client pair, with the frame parsing in about a page of code. diff --git a/docs/selkies/index.md b/docs/selkies/index.md new file mode 100644 index 00000000..8dd2f680 --- /dev/null +++ b/docs/selkies/index.md @@ -0,0 +1,119 @@ +# Selkies Platform Documentation + +Welcome to the documentation for the **Selkies platform** as maintained and shipped by [LinuxServer.io](https://www.linuxserver.io). Selkies is a web native remote desktop protocol and streaming stack that delivers full Linux desktops and single Linux applications to any modern web browser, with no client software to install. + +This is the stack behind [Webtop](https://github.com/linuxserver/docker-webtop), the popular remote browser containers like [Chromium](https://github.com/linuxserver/docker-chromium) and [Firefox](https://github.com/linuxserver/docker-firefox), and more than one hundred single application containers. It is built from years of iteration on remote desktop delivery and is designed to be the go to standard for shipping a Linux desktop or Linux application to a web browser. + +## Quick taste + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +Then open `https://localhost:3001` in your browser and accept the self signed certificate. That is the entire install, and `ctrl+d` in the terminal tears it all down again. + +## Where to start + +- **I want to run an app or desktop in my browser** — start with the [Quickstart](user-guide/quickstart.md). One docker command and you are streaming a desktop in under a minute. +- **I want to configure, secure, or GPU accelerate my container** — the [User Guide](user-guide/index.md) covers installation, GPU passthrough, the web client, hardening, and troubleshooting. +- **I want to build my own image or integrate the platform** — the [Developer Guide](developer-guide/index.md) explains the architecture, the baseimage internals, and how to package your own application or desktop. +- **I want to understand the pieces** — the [Components](components/index.md) section documents every subproject: Selkies, Pixelflux, the baseimages, SealSkin, Selkies Desktop, and Pelorus. + +## The problem + +Delivering a Linux desktop or GUI application to a remote user has historically meant choosing between bad options: + +- **VNC** family protocols are universally compatible but struggle with motion, video playback, and modern latency expectations. +- **RDP and proprietary protocols** need dedicated client software and often licensing. +- **Plain WebRTC video streaming** treats the desktop like a video call. It handles motion well but wastes bandwidth and CPU on static screens, fights with congestion control that was tuned for cameras, and blurs text. + +A desktop is not a movie. Most of the time nothing on screen is changing, and when it does change it is often a small region, a scrolling document, or a burst of full motion. The Selkies platform is built around that reality. + +## The approach + +Selkies is a ground up, web native remote desktop protocol designed to replace legacy VNC stacks. The core ideas: + +1. **Hybrid protocol.** Damage tracking like VNC, video codecs like a streaming service. The screen is divided into horizontal stripes, only changed stripes are captured and encoded, and each stripe can be processed on a separate CPU core in parallel. +2. **Paint over quality.** H.264 handles fluid motion, and once motion stops the server repaints the static screen at high quality so text stays crisp. With FullColor 4:4:4 H.264 the painted over result is visually indistinguishable from a lossless image. A JPEG encoder remains available for older browsers that cannot decode video frames at all. +3. **WebSockets, not WebRTC.** Frames are delivered over a WebSocket connection and decoded in the browser with WebCodecs. This avoids WebRTC negotiation complexity, works cleanly through reverse proxies, and gives the server precise control over pacing and backpressure. +4. **Zero copy on Wayland.** In the current generation the display server is a virtual Wayland compositor built on [Smithay](https://github.com/Smithay/smithay). The framebuffer can live directly on a GPU, and frames are passed as DMA-BUF handles straight to the hardware encoder (VAAPI or NVENC) without a round trip through system RAM. +5. **Everything in one container.** Compositor, application, streaming server, audio, and web server all run inside a single OCI container built on `docker-baseimage-selkies`, managed by the s6 init system. + +## What the platform gives you + +- **A desktop in the browser.** Full desktop environments (KDE Plasma, XFCE, MATE, i3, and more) or single applications streamed over WebSockets with H.264 encoding. +- **Zero copy GPU encoding.** On the Wayland stack, frames are rendered and encoded on the GPU without ever touching system RAM, for Intel, AMD, and Nvidia hardware. +- **Runs anywhere.** The CPU encoding path is efficient enough to serve 1080p60 sessions from budget mini PCs and ARM boards. A GPU is optional, not required. +- **A complete client, not just video.** Audio in both directions, clipboard sync, file upload and download, gamepad passthrough for up to four players, touch and virtual trackpad support for mobile, IME input, and multi user session sharing. +- **Containers first.** Everything ships as OCI images built on the LinuxServer.io baseimage ecosystem, with the same PUID, PGID, and volume conventions used across all LinuxServer images. +- **An orchestration story.** [SealSkin](components/sealskin.md) provides a turnkey multi user VDI layer on top of these images, and serves as a reference implementation if you want to build your own. +- **An agentic story.** [Pelorus](components/pelorus.md) exposes desktops to LLM agents with text based desktop state and a computer use API. + +## The layers + +From the browser down to the application: + +| Layer | Component | What it does | +| --- | --- | --- | +| Client | Selkies web client (dashboard) | Renders video with WebCodecs, plays Opus audio, captures input, provides the sidebar UI, file transfer, clipboard, gamepads, and sharing | +| Transport | WebSockets over HTTPS | Binary video, audio, and input messages, fronted by Nginx inside the container | +| Server | Selkies (Python) | Session orchestration: wires capture to the socket, injects input, manages clipboard, files, and settings | +| Video | Pixelflux (Rust with Python bindings) | Captures the framebuffer, detects damage, encodes H.264 or JPEG, CPU or GPU. In Wayland mode pixelflux itself hosts the compositor | +| Audio | Pcmflux | Captures PulseAudio output and encodes Opus for the browser, plus microphone return | +| Display server | Smithay based Wayland compositor (inside pixelflux) | Virtual framebuffer in userspace, on GPU or CPU, replaces Xvfb from the X11 era | +| Window management | labwc (single apps) or KDE Plasma (desktops) | Window decoration, tiling, desktop shell | +| Packaging | docker-baseimage-selkies | s6 services, Nginx, auth, GPU detection, user management, all the LinuxServer.io container conventions | + +## The stack at a glance + +```mermaid +graph TD + B[Web Browser] -->|HTTPS / WebSockets| N[Nginx in container] + N --> S[Selkies Python server] + S --> PF[Pixelflux video capture and encode] + S --> PC[Pcmflux audio capture and encode] + S --> I[Input injection] + PF --> C[Wayland compositor, Smithay plus labwc or KDE] + C --> A[Your application or desktop] + subgraph Container based on docker-baseimage-selkies + N + S + PF + PC + I + C + A + end +``` + +Every layer of that diagram has its own page in the [Components](components/index.md) section. + +## Single application vs full desktop + +The platform ships in two flavors that share the same machinery: + +- **Single application containers** such as `linuxserver/chromium` or `linuxserver/firefox` run one app under the lightweight labwc compositor, usually maximized and undecorated so the app looks native in the browser tab. The optional [Selkies Desktop](components/selkies-desktop.md) shell adds a minimal panel, start menu, and wallpaper when you want light multi window capability without a full desktop environment. +- **Webtop containers** run a complete desktop environment. The flagship experience is KDE Plasma running natively on Wayland (the `ubuntu-kde` tag), with additional flavors across Ubuntu, Debian, Fedora, Arch, and Alpine bases. See the [support matrix](user-guide/apps.md#webtop-full-desktops) for which flavors run Wayland today. + +## Beyond a single container + +- **[SealSkin](components/sealskin.md)** is the orchestration layer: a self hosted server that manages users, launches and reaps app containers on demand, handles authentication with public key cryptography, and integrates with browser extensions and mobile apps so that clicking a link or opening a file can transparently launch an isolated remote app. Use it as a turnkey VDI, or read its source as a reference for building your own platform on these images. +- **[Pelorus](components/pelorus.md)** turns any Selkies desktop into an agent operable environment. The desktop and its applications are represented as text so a chat LLM can drive them, with screenshots available when vision is needed. It supports labwc single app sessions and full KDE desktops and can be enabled with a single environment variable. +- **[Pixelflux](components/pixelflux.md)** and pcmflux are published as standalone Python packages, so the capture and encode pipeline can be embedded in your own projects without any of the container tooling. + +## Design values + +- **Minimal commands first.** Every container should work with `docker run --rm -it --shm-size=1gb -p 3001:3001 lscr.io/linuxserver/ bash`. GPU flags, volumes, and tuning are additive layers, not prerequisites. This is also the debugging philosophy: strip back to the minimal command, confirm it works, then add options one at a time. +- **CPU is a first class citizen.** The platform is tuned so that commodity hardware without any GPU can serve smooth sessions. Hardware encoding is an optimization, never a requirement. +- **All in on Wayland.** The Wayland stack unlocks true zero copy from render to encode and the performance difference over X11 is night and day. X11 remains only as a legacy fallback and will die off eventually. +- **HTTPS always.** Modern browser APIs used by the client (WebCodecs in particular) require a secure context. Every container serves HTTPS with a self signed certificate on port 3001 out of the box. +- **Open source end to end.** Every layer, from the compositor to the mobile apps, is open source and developed in public. + +## About this documentation + +- The **User Guide** is for people running the prebuilt containers. +- The **Developer Guide** is for people building images on top of the baseimages or integrating the underlying libraries. +- The **Components** section is a map of the subprojects that make up the whole platform, with a page for each. diff --git a/docs/selkies/user-guide/.pages b/docs/selkies/user-guide/.pages new file mode 100644 index 00000000..96319286 --- /dev/null +++ b/docs/selkies/user-guide/.pages @@ -0,0 +1,12 @@ +nav: + - index.md + - Quickstart: quickstart.md + - Installation: installation.md + - GPU Acceleration: gpu.md + - Using the Web Client: web-client.md + - Configuration Reference: configuration.md + - Available Apps and Desktops: apps.md + - Installing Applications: installing-apps.md + - Security and Hardening: security.md + - Reverse Proxy: reverse-proxy.md + - Troubleshooting: troubleshooting.md diff --git a/docs/selkies/user-guide/apps.md b/docs/selkies/user-guide/apps.md new file mode 100644 index 00000000..3dc4e678 --- /dev/null +++ b/docs/selkies/user-guide/apps.md @@ -0,0 +1,81 @@ +# Available Apps and Desktops + +Every image in this catalog is built on the same [Selkies baseimage](../components/baseimages.md), so everything in this guide, the ports, the environment variables, the GPU flags, the web client, applies to all of them identically. Swap the image name and you are done. + +## Webtop: full desktops + +[Webtop](https://github.com/linuxserver/docker-webtop) ships complete desktop environments as tags of `lscr.io/linuxserver/webtop`. One branch per flavor, the tag is `-`: + +| Tag | Description | +| --- | --- | +| `latest` | XFCE Alpine *Wayland Support | +| `alpine-i3` | i3 Alpine *Wayland Support | +| `alpine-kde` | KDE Alpine *Wayland Only | +| `alpine-mate` | MATE Alpine | +| `arch-i3` | i3 Arch *Wayland Support | +| `arch-kde` | KDE Arch *Wayland Support | +| `arch-mate` | MATE Arch | +| `arch-xfce` | XFCE Arch *Wayland Support | +| `debian-i3` | i3 Debian *Wayland Support | +| `debian-kde` | KDE Debian | +| `debian-mate` | MATE Debian | +| `debian-xfce` | XFCE Debian | +| `fedora-i3` | i3 Fedora *Wayland Support | +| `fedora-kde` | KDE Fedora *Wayland Support | +| `fedora-mate` | MATE Fedora | +| `fedora-xfce` | XFCE Fedora | +| `ubuntu-i3` | i3 Ubuntu *Wayland Support | +| `ubuntu-kde` | KDE Ubuntu *Wayland Only | +| `ubuntu-mate` | MATE Ubuntu | +| `ubuntu-xfce` | XFCE Ubuntu *Wayland Support | + +How to read the matrix: + +- **Wayland Support** means the flavor can run the modern Wayland stack. XFCE flavors marked this way carry experimental XFCE Wayland support, and the i3 flavors run **Sway** when in Wayland mode. +- **Wayland Only** flavors cannot use X11 at all. All KDE images now run in Wayland mode by default. +- Unmarked flavors run the X11 stack. X11 will die off eventually, the platform is all in on Wayland because of the true zero copy pipeline it unlocks, the performance difference is night and day. Prefer a Wayland capable flavor whenever you can. + +```bash +docker run -d \ + --name=webtop \ + --shm-size=1gb \ + -p 3001:3001 \ + -v /path/to/config:/config \ + lscr.io/linuxserver/webtop:ubuntu-kde +``` + +Notes: + +- The **KDE flavors are the flagship experience**, running KDE Plasma natively on Wayland with a nested KWin compositor. `ubuntu-kde` reflects the current state of the project best and is the reference flavor used throughout these docs. +- **Nvidia GPU support is not available on Alpine based images.** GPU users should pick Ubuntu, Debian, Fedora, or Arch flavors. + +## Single application images + +More than one hundred applications are packaged as dedicated images, each running the app under the lightweight labwc compositor so it feels like a native web app. All follow the pattern `lscr.io/linuxserver/`: + +**Browsers and web:** Brave, Chrome, Chromium, Firefox, Helium, Librewolf, Msedge, Mullvad Browser, Opera, Ungoogled Chromium, Vivaldi, Zen, Webstation + +**Communication:** Altus, Ferdium, Pidgin, Signal, Telegram, Thunderbird, Webcord, Weixin + +**Development:** Github Desktop, Gitqlient, Intellij Idea, Mysql Workbench, Pycharm, Sqlitebrowser, Vscode, Vscodium, Yaak + +**Creative and media:** Ardour, Audacity, Blender, Darktable, Digikam, Gimp, Handbrake, Inkscape, Kdenlive, Krita, Lollypop, Rawtherapee, Shotcut, Spotube, Vlc + +**Office and productivity:** Calibre, Calligra, Joplin, Libreoffice, Obsidian, Onlyoffice, Wps Office, Zotero + +**3D printing and engineering:** Bambustudio, Cura, Freecad, Kicad, Orcaslicer + +**Gaming and emulation:** Azahar, Blade of Agony, Dolphin, Dosbox Staging, Duckstation, Eden, Flycast, Gzdoom, Mame, Melonds, Modrinth, Pcsx2, Ppsspp, Retroarch, Rpcs3, shadPS4, Steam, Xemu, Dogwalk + +**Utilities and other:** Boinc, Doublecommander, Filezilla, Keepassxc, Kali Linux, Mediaelch, Qdirstat, Remmina, Rustdesk, Wireshark, WineGUI + +Each app has its own GitHub repository at `github.com/linuxserver/docker-` with a README covering any app specific options (for example `CHROME_CLI` on the Chromium image to pass command line flags to the browser). + +The machine readable list that powers the [SealSkin](../components/sealskin.md) app store lives at [sealskin.app/apps.js](https://sealskin.app/apps.js), and the SealSkin apps registry with full launch metadata is documented on the [SealSkin Apps Registry](../components/sealskin-apps.md) page. + +## Which should I pick? + +- **You want one app in a browser tab** (a remote browser, a media tool, an IDE): use the dedicated app image. Lower memory, instant focus on the app, kiosk friendly. +- **You want one app but occasionally need a file manager or second window:** use the app image and set `-e SELKIES_DESKTOP=true` for the minimal [Selkies Desktop](../components/selkies-desktop.md) shell. +- **You want a real desktop:** Webtop. Pick `ubuntu-kde` or `debian-kde` if in doubt, or an XFCE or MATE flavor for lower resource usage. +- **You want many apps for many users, launched on demand:** run [SealSkin](../components/sealskin.md) and let it orchestrate all of the above. diff --git a/docs/selkies/user-guide/configuration.md b/docs/selkies/user-guide/configuration.md new file mode 100644 index 00000000..5762c143 --- /dev/null +++ b/docs/selkies/user-guide/configuration.md @@ -0,0 +1,174 @@ +# Configuration Reference + +Every Selkies based container is configured through environment variables. This page collects all of them in one place. They fall into three groups: + +1. **Container variables**: standard LinuxServer.io conventions plus Selkies baseimage options (ports, auth, GPU, language). +2. **Selkies application settings** (`SELKIES_*`): stream, client UI, and feature toggles, with a locking syntax. +3. **Hardening variables**: lockdown options covered in detail on the [Security page](security.md). + +## Standard LinuxServer variables + +These work in every LinuxServer.io container: + +| Variable | Default | Description | +| --- | --- | --- | +| `PUID` | `911` | User ID the in container user `abc` runs as, match it to your host user for sane volume ownership | +| `PGID` | `911` | Group ID for `abc` | +| `TZ` | `Etc/UTC` | Timezone, e.g. `Europe/London` | +| `DOCKER_MODS` | unset | Layer additional functionality at runtime, see [Installing Applications](installing-apps.md) | + +## Baseimage variables + +| Variable | Description | +| --- | --- | +| `PIXELFLUX_WAYLAND` | If set to true the container will initialize in Wayland mode running [Smithay](https://github.com/Smithay/smithay) and labwc while enabling zero copy encoding with a GPU. This is the default on supported hardware; set `false` to force legacy X11 | +| `SELKIES_DESKTOP` | If set to true and in Wayland mode, a simple desktop shell (panel, start menu, wallpaper, desktop icons) is initialized with labwc, see [Selkies Desktop](../components/selkies-desktop.md) | +| `PELORUS` | If set to true, the [Pelorus](../components/pelorus.md) agentic interface and accessibility stack are started alongside the session | +| `CUSTOM_PORT` | Internal HTTP port, default `3000` | +| `CUSTOM_HTTPS_PORT` | Internal HTTPS port, default `3001` | +| `CUSTOM_WS_PORT` | Internal WebSocket port, default `8082` | +| `CUSTOM_USER` | HTTP basic auth username, default `abc` | +| `PASSWORD` | HTTP basic auth password, default `abc`. If unset there is no auth | +| `DRI_NODE` | Encoding GPU, enables VAAPI/NVENC stream encoding on the given device, e.g. `/dev/dri/renderD128` | +| `DRINODE` | Rendering GPU for EGL and 3D acceleration, e.g. `/dev/dri/renderD128` | +| `AUTO_GPU` | Automatic GPU configuration when one is detected, first available GPU used for encoding and rendering. Set `false` to disable | +| `PIXELFLUX_CU` | Port to enable the Computer Use API server for AI agent control of the desktop, Wayland mode only | +| `SUBFOLDER` | Subfolder when running behind a subfolder reverse proxy, needs both slashes, e.g. `/subfolder/` | +| `TITLE` | Page title shown in the browser, default `Selkies` | +| `DASHBOARD` | Select the web client dashboard: `selkies-dashboard`, `selkies-dashboard-zinc`, or `selkies-dashboard-wish` | +| `FILE_MANAGER_PATH` | Change the default upload and download path, must be writable by the `abc` user | +| `START_DOCKER` | If `false`, a privileged container will not automatically start the Docker in Docker setup | +| `DISABLE_IPV6` | Set to `true` or any value to disable IPv6 | +| `LC_ALL` | Session language, e.g. `fr_FR.UTF-8`, see [Internationalization](#internationalization) | +| `NO_DECOR` | Run the application without window borders, for PWA style use. Toggle at runtime with `ctrl+shift+d` | +| `NO_FULL` | Do not automatically fullscreen applications when using the single app window manager | +| `NO_GAMEPAD` | Disable the userspace gamepad interposer injection | +| `DISABLE_ZINK` | Do not set Zink variables when a GPU is detected, applications use CPU rendering | +| `DISABLE_DRI3` | X11 mode only, disable DRI3 acceleration | +| `MAX_RES` | Maximum virtual display resolution, default 16K (`15360x8640`) | +| `WATERMARK_PNG` | Full path inside the container to a watermark PNG, e.g. `/usr/share/selkies/www/icon.png` | +| `WATERMARK_LOCATION` | Where to paint the watermark, integer 1 to 6 | + +**`WATERMARK_LOCATION` values:** `1` top left, `2` top right, `3` bottom left, `4` bottom right, `5` centered, `6` animated. + +## Selkies application settings + +Every facet of the streaming application can be configured with `SELKIES_*` variables. These also drive what the user can change in the sidebar UI. + +### Value syntax + +- **Booleans and locking.** Booleans accept `true` or `false`. Append `|locked` to prevent the user changing the setting in the UI: `-e SELKIES_USE_CPU="true|locked"` +- **Enums and lists.** Comma separated values, the first item is the default. A single item hides the UI dropdown entirely: `-e SELKIES_ENCODER="jpeg"` +- **Ranges.** `min-max` renders a slider, a single number locks the value: `-e SELKIES_FRAMERATE="60"` +- **Manual resolution.** Setting `SELKIES_MANUAL_WIDTH` or `SELKIES_MANUAL_HEIGHT` locks the resolution to those values. + +### Stream settings + +| Variable | Default | Description | +| --- | --- | --- | +| `SELKIES_ENCODER` | `'x264enc,x264enc-striped,jpeg'` | Available video encoders, first is default | +| `SELKIES_FRAMERATE` | `'8-120'` | Framerate range or fixed value | +| `SELKIES_H264_CRF` | `'5-50'` | H.264 CRF range or fixed value, lower is higher quality | +| `SELKIES_JPEG_QUALITY` | `'1-100'` | JPEG quality range or fixed value | +| `SELKIES_H264_FULLCOLOR` | `False` | H.264 full color 4:4:4 range for pixelflux encoders | +| `SELKIES_H264_STREAMING_MODE` | `False` | H.264 streaming mode for pixelflux encoders | +| `SELKIES_FORCE_ALIGNED_RESOLUTION` | `False` | Forces the display resolution to be a multiple of 16 pixels. | +| `SELKIES_USE_CPU` | `False` | Force CPU encoding | +| `SELKIES_USE_PAINT_OVER_QUALITY` | `True` | High quality paint over for static scenes | +| `SELKIES_PAINT_OVER_JPEG_QUALITY` | `'1-100'` | JPEG paint over quality range or fixed value | +| `SELKIES_H264_PAINTOVER_CRF` | `'5-50'` | H.264 paint over CRF range or fixed value | +| `SELKIES_H264_PAINTOVER_BURST_FRAMES` | `'1-30'` | H.264 paint over burst frames range or fixed value | +| `SELKIES_SECOND_SCREEN` | `True` | Support for a second monitor | +| `SELKIES_AUDIO_BITRATE` | `'320000'` | Default audio bitrate | +| `SELKIES_IS_MANUAL_RESOLUTION_MODE` | `False` | Lock resolution to the manual width and height | +| `SELKIES_MANUAL_WIDTH` | `0` | Fixed width, setting this forces manual resolution mode | +| `SELKIES_MANUAL_HEIGHT` | `0` | Fixed height, setting this forces manual resolution mode | +| `SELKIES_SCALING_DPI` | `'96'` | Default DPI for UI scaling | +| `SELKIES_USE_BROWSER_CURSORS` | `False` | Use browser CSS cursors instead of canvas rendering | +| `SELKIES_USE_CSS_SCALING` | `False` | HiDPI when false. When true a lower resolution is sent and the canvas is stretched | + +### Feature toggles + +| Variable | Default | Description | +| --- | --- | --- | +| `SELKIES_AUDIO_ENABLED` | `True` | Server to client audio streaming | +| `SELKIES_MICROPHONE_ENABLED` | `True` | Client to server microphone forwarding | +| `SELKIES_GAMEPAD_ENABLED` | `True` | Gamepad support | +| `SELKIES_CLIPBOARD_ENABLED` | `True` | Clipboard synchronization | +| `SELKIES_ENABLE_BINARY_CLIPBOARD` | `False` | Allow binary data on the clipboard | +| `SELKIES_COMMAND_ENABLED` | `True` | Parsing of command websocket messages | +| `SELKIES_FILE_TRANSFERS` | `'upload,download'` | Allowed transfer directions, comma separated. Empty or `none` disables | +| `SELKIES_DEBUG` | `False` | Debug logging | + +### Sharing toggles + +| Variable | Default | Description | +| --- | --- | --- | +| `SELKIES_ENABLE_SHARING` | `True` | Master toggle for all sharing features | +| `SELKIES_ENABLE_COLLAB` | `True` | Collaborative read write sharing link | +| `SELKIES_ENABLE_SHARED` | `True` | View only sharing links | +| `SELKIES_ENABLE_PLAYER2` | `True` | Sharing link for gamepad player 2 | +| `SELKIES_ENABLE_PLAYER3` | `True` | Sharing link for gamepad player 3 | +| `SELKIES_ENABLE_PLAYER4` | `True` | Sharing link for gamepad player 4 | + +### Client UI visibility + +| Variable | Default | Description | +| --- | --- | --- | +| `SELKIES_UI_TITLE` | `'Selkies'` | Title in the top left corner of the sidebar | +| `SELKIES_UI_SHOW_LOGO` | `True` | Show the Selkies logo in the sidebar | +| `SELKIES_UI_SHOW_SIDEBAR` | `True` | Show the main sidebar UI | +| `SELKIES_UI_SHOW_CORE_BUTTONS` | `True` | Show display, audio, microphone, and gamepad core buttons | +| `SELKIES_UI_SIDEBAR_SHOW_VIDEO_SETTINGS` | `True` | Video settings section | +| `SELKIES_UI_SIDEBAR_SHOW_SCREEN_SETTINGS` | `True` | Screen settings section | +| `SELKIES_UI_SIDEBAR_SHOW_AUDIO_SETTINGS` | `True` | Audio settings section | +| `SELKIES_UI_SIDEBAR_SHOW_STATS` | `True` | Stats section | +| `SELKIES_UI_SIDEBAR_SHOW_CLIPBOARD` | `True` | Clipboard section | +| `SELKIES_UI_SIDEBAR_SHOW_FILES` | `True` | File transfer section | +| `SELKIES_UI_SIDEBAR_SHOW_APPS` | `True` | Applications section | +| `SELKIES_UI_SIDEBAR_SHOW_SHARING` | `True` | Sharing section | +| `SELKIES_UI_SIDEBAR_SHOW_GAMEPADS` | `True` | Gamepads section | +| `SELKIES_UI_SIDEBAR_SHOW_FULLSCREEN` | `True` | Fullscreen button | +| `SELKIES_UI_SIDEBAR_SHOW_GAMING_MODE` | `True` | Gaming mode button | +| `SELKIES_UI_SIDEBAR_SHOW_TRACKPAD` | `True` | Virtual trackpad button | +| `SELKIES_UI_SIDEBAR_SHOW_KEYBOARD_BUTTON` | `True` | On screen keyboard button in the display area | +| `SELKIES_UI_SIDEBAR_SHOW_SOFT_BUTTONS` | `True` | Soft buttons section | + +### Plumbing + +| Variable | Default | Description | +| --- | --- | --- | +| `SELKIES_PORT` (or `CUSTOM_WS_PORT`) | `8082` | Data WebSocket server port | +| `SELKIES_DRI_NODE` (or `DRI_NODE`) | `''` | DRI render node for VA-API | +| `SELKIES_AUDIO_DEVICE_NAME` | `'output.monitor'` | Audio device for pcmflux capture | +| `SELKIES_WATERMARK_PATH` (or `WATERMARK_PNG`) | `''` | Absolute path to watermark PNG | +| `SELKIES_WATERMARK_LOCATION` (or `WATERMARK_LOCATION`) | `-1` | Watermark location enum 0 to 6 | + +## Optional run configurations + +| Argument | Description | +| --- | --- | +| `--privileged` | Starts a Docker in Docker environment inside the container. For better performance mount the Docker data directory from the host, e.g. `-v /path/to/docker-data:/var/lib/docker` | +| `-v /var/run/docker.sock:/var/run/docker.sock` | Manage host containers from inside this container | +| `--device /dev/dri:/dev/dri` | Mount a GPU, combine with `DRINODE` to pick a card | +| `--shm-size=1gb` | Required for Electron apps and browsers | +| `--security-opt seccomp=unconfined` | Last resort for older kernels or libseccomp versions where modern syscalls are blocked | + +## Internationalization + +Set `LC_ALL` to launch the session in another language: + +- `-e LC_ALL=zh_CN.UTF-8` Chinese +- `-e LC_ALL=ja_JP.UTF-8` Japanese +- `-e LC_ALL=ko_KR.UTF-8` Korean +- `-e LC_ALL=ar_AE.UTF-8` Arabic +- `-e LC_ALL=ru_RU.UTF-8` Russian +- `-e LC_ALL=es_MX.UTF-8` Spanish (Latin America) +- `-e LC_ALL=de_DE.UTF-8` German +- `-e LC_ALL=fr_FR.UTF-8` French +- `-e LC_ALL=nl_NL.UTF-8` Dutch +- `-e LC_ALL=it_IT.UTF-8` Italian + +## Hardening variables + +`HARDEN_DESKTOP`, `HARDEN_OPENBOX`, `DISABLE_SUDO`, `DISABLE_TERMINALS`, `RESTART_APP`, and friends are documented with context on the [Security and Hardening](security.md) page. diff --git a/docs/selkies/user-guide/gpu.md b/docs/selkies/user-guide/gpu.md new file mode 100644 index 00000000..997183fc --- /dev/null +++ b/docs/selkies/user-guide/gpu.md @@ -0,0 +1,159 @@ +# GPU Acceleration + +A GPU is optional. The CPU pipeline is fast enough for smooth desktop sessions on modest hardware. Add a GPU when you want 3D applications, gaming, hardware video decode inside apps, or the lowest possible CPU usage through zero copy encoding. + +## The two GPU jobs + +The platform uses a GPU for two separate jobs, controlled by two separate variables: + +| Variable | Job | Meaning | +| --- | --- | --- | +| `DRINODE` | **Rendering** | Which GPU applications use for EGL and 3D acceleration, e.g. `/dev/dri/renderD128` | +| `DRI_NODE` | **Encoding** | Which GPU encodes the video stream (VAAPI or NVENC), e.g. `/dev/dri/renderD128` | + +Yes, the names differ by one underscore. `DRINODE` renders, `DRI_NODE` encodes. + +- If **both point at the same device**, the container enables **zero copy** encoding: the frame is rendered and encoded on the card without ever being copied to system RAM. This drastically lowers CPU usage and latency. +- If they point at **different devices**, one card renders and the other encodes, with a CPU readback in between. Useful, but not zero copy. + +## Automatic configuration + +You usually do not need to set either variable. When a GPU is mounted into the container it is detected and configured automatically for both rendering and encoding with zero copy (`AUTO_GPU` behavior, the first available GPU is used). + +To mount a GPU but *not* use it, set `AUTO_GPU=false`. + +## Intel and AMD (open source drivers) + +The simple case. Mount the DRI devices and you are done: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + --device /dev/dri \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +Compose: + +```yaml + devices: + - /dev/dri:/dev/dri +``` + +For multi GPU systems, list the render nodes on the host (`ls /dev/dri`) and pin the one you want: + +```yaml + environment: + - DRINODE=/dev/dri/renderD129 + - DRI_NODE=/dev/dri/renderD129 +``` + +## Nvidia (proprietary drivers) + +!!! warning "Prerequisites matter here" + Nvidia is the platform where "run the minimal command first" pays off most. Get the host driver right before touching compose files. + +**Not available on Alpine based images.** + +Whatever the driver version, install it from the `.run` file downloaded directly from Nvidia. Distribution packaged drivers frequently cause problems. On Unraid, use the production branch of the Nvidia Driver Plugin. + +### Driver 595.80 and newer + +This is the easy path and where the platform is headed: no kernel parameters, no dummy plugs. The only host requirement is that `/dev/nvidia-modeset` exists, on some systems you may need to run this once (or at boot) to activate the device: + +```bash +sudo nvidia-modprobe --modeset +``` + +Then mount it alongside the GPU. `--device /dev/nvidia-modeset` is required for proper Vulkan support: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + --runtime nvidia \ + --gpus all \ + --device /dev/nvidia-modeset \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +### Driver 580 to 594 + +Older drivers work but need host preparation: + +1. **Kernel parameters**: set `nvidia-drm.modeset=1 nvidia_drm.fbdev=1` on the host bootloader. + + On GRUB systems, edit `/etc/default/grub`: + + ```text + GRUB_CMDLINE_LINUX_DEFAULT=" nvidia-drm.modeset=1 nvidia_drm.fbdev=1" + ``` + + then `sudo update-grub` and reboot. On Unraid, add the same string to the `append` line in `/boot/syslinux/syslinux.cfg`. + +2. **Dummy plug**: on truly headless machines, the card may need a physical dummy plug inserted so DRM initializes properly. +3. **`/dev/nvidia-modeset`** may still need to be passed, same run command as above. + +### Compose + +First configure the Nvidia runtime on the host: + +```bash +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +```yaml +--- +services: + webtop: + image: lscr.io/linuxserver/webtop:ubuntu-kde + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [compute,video,graphics,utility] +``` + +On Unraid, set `DRINODE` and `DRI_NODE` appropriately and add `--gpus all --runtime nvidia` to the extra parameters. + +## FullColor 4:4:4 and hardware encoders + +If you notice blurry text, especially light text on dark backgrounds, enable **FullColor 4:4:4** encoding in the sidebar, or use the JPEG encoder. This sends true 8 bit color to the browser. + +One caveat: only Nvidia GPUs can encode 4:4:4 in zero copy mode. Enabling FullColor on Intel or AMD falls back to CPU encoding, which forces a pixel readback from the GPU and costs significant performance. On those cards, prefer the default 4:2:0 for motion and let paint over handle static clarity. + +## Wayland and X11 + +The Wayland stack is the default and is where all GPU acceleration development happens. You can force the legacy X11 stack with `-e PIXELFLUX_WAYLAND=false`, but GPU acceleration under X11 is not currently seeing development attention. If you are on X11 and using acceleration, clamp the virtual display to avoid memory exhaustion, e.g. `-e MAX_RES=3840x2160`, and if you still have problems lock the resolution down: + +```bash +-e SELKIES_MANUAL_WIDTH=1920 +-e SELKIES_MANUAL_HEIGHT=1080 +-e MAX_RES=1920x1080 +``` + +## Related environment variables + +| Variable | Description | +| --- | --- | +| `AUTO_GPU` | Auto detection of a mounted GPU for rendering and encoding, enabled by default. Set `false` to disable. | +| `DRINODE` | Rendering GPU (EGL / 3D). | +| `DRI_NODE` | Encoding GPU (VAAPI / NVENC). | +| `DISABLE_ZINK` | X11 mode only, do not set Zink variables when a card is detected, applications fall back to CPU rendering. | +| `DISABLE_DRI3` | X11 mode only, disable DRI3 acceleration, applications fall back to CPU rendering. | +| `PIXELFLUX_WAYLAND` | `true` is the modern Wayland stack with zero copy support, `false` forces legacy X11. | + +## Debugging GPU problems + +Follow the minimal command loop from the [Quickstart](quickstart.md): + +1. Run the container with **no** GPU flags. Confirm the base experience works with CPU encoding. +2. Add only the device mount (`--device /dev/dri` or the Nvidia trio). Check container logs for GPU detection messages. +3. Inside the session, open a terminal and run `vkcube`. A spinning cube at full speed means rendering on the card works, this is the go to in container GPU test. `vainfo` (Intel/AMD) and `nvidia-smi` (Nvidia) confirm the container sees the card and its encode capabilities. +4. Only then start pinning `DRINODE` and `DRI_NODE` manually. + +If the container works without the GPU flags and breaks with them, the problem is host side: driver version, kernel parameters, or device permissions. diff --git a/docs/selkies/user-guide/index.md b/docs/selkies/user-guide/index.md new file mode 100644 index 00000000..8aa431a2 --- /dev/null +++ b/docs/selkies/user-guide/index.md @@ -0,0 +1,18 @@ +# User Guide + +This guide is for people running the prebuilt Selkies based containers from LinuxServer.io: the Webtop desktops and the single application images like Chromium, Firefox, GIMP, or Steam. + +You do not need to know anything about the internals to use these containers. If a page in this guide feels too deep, back up to the [Quickstart](quickstart.md), it is genuinely enough for most people. + +## Reading order + +1. **[Quickstart](quickstart.md)**: get a desktop or app streaming in one command, and learn the minimal command debugging philosophy. +2. **[Installation](installation.md)**: docker run and compose in full, ports, volumes, PUID and PGID, and the options that actually matter. +3. **[GPU Acceleration](gpu.md)**: Intel, AMD, and Nvidia passthrough, zero copy encoding, and multi GPU setups. +4. **[Using the Web Client](web-client.md)**: the sidebar, clipboard, file transfer, gamepads, mobile controls, and session sharing. +5. **[Configuration Reference](configuration.md)**: every environment variable in one place. +6. **[Available Apps and Desktops](apps.md)**: the Webtop flavors and the full application catalog. +7. **[Installing Applications](installing-apps.md)**: adding software inside a container with proot-apps or Docker mods. +8. **[Security and Hardening](security.md)**: authentication, what the container can do, and lockdown variables for kiosk style deployments. +9. **[Reverse Proxy](reverse-proxy.md)**: putting containers behind SWAG, Nginx, Traefik, or a subfolder. +10. **[Troubleshooting](troubleshooting.md)**: the checklist to run before opening an issue. diff --git a/docs/selkies/user-guide/installation.md b/docs/selkies/user-guide/installation.md new file mode 100644 index 00000000..ad72eccb --- /dev/null +++ b/docs/selkies/user-guide/installation.md @@ -0,0 +1,106 @@ +# Installation + +This page covers running the prebuilt containers properly for long term use. If you have not run one at all yet, do the [Quickstart](quickstart.md) first. + +## Requirements + +- A modern browser on the client. The client uses WebCodecs and other current APIs; recent Chrome, Edge, Brave, Vivaldi, Firefox, and Safari work. Chromium family browsers give the best experience. +- Docker (docker-ce) on any x86_64 or aarch64 Linux host. Alternative runtimes such as Podman may work but are not officially supported, we support docker-ce. +- No GPU required. See [GPU Acceleration](gpu.md) when you want one. + +## docker run + +```bash +docker run -d \ + --name=webtop \ + --shm-size=1gb \ + -e PUID=1000 \ + -e PGID=1000 \ + -e TZ=Etc/UTC \ + -p 3001:3001 \ + -v /path/to/config:/config \ + --restart unless-stopped \ + lscr.io/linuxserver/webtop:ubuntu-kde +``` + +## docker compose + +```yaml +--- +services: + webtop: + image: lscr.io/linuxserver/webtop:ubuntu-kde + container_name: webtop + shm_size: 1gb + environment: + - PUID=1000 + - PGID=1000 + - TZ=Etc/UTC + volumes: + - /path/to/config:/config + ports: + - 3001:3001 + restart: unless-stopped +``` + +Then browse to `https://:3001`. + +## The conventions, explained + +These containers follow every LinuxServer.io convention, so if you have run any LSIO image before, nothing here will surprise you. + +### Ports + +| Port | Protocol | Purpose | +| --- | --- | --- | +| `3001` | HTTPS | The port you use. Self signed certificate out of the box | +| `3000` | HTTP | Only for use behind a reverse proxy that terminates TLS. Browsing to it directly gives you a broken client, because the required browser APIs need a secure context | + +Both are served by an Nginx instance inside the container which also proxies the internal WebSocket streaming port. You never need to publish anything else. `CUSTOM_PORT`, `CUSTOM_HTTPS_PORT`, and `CUSTOM_WS_PORT` change the internal ports if they collide with something in your network stack (mostly relevant with `network_mode: host`). + +### The /config volume + +`/config` is the in container home directory of the desktop user (`abc`). Browser profiles, application settings, downloads, everything the user touches lives here. **Anything outside `/config` is lost on image update.** That is by design, see [Installing Applications](installing-apps.md) for the persistence options. + +A useful property: `/config` is portable across image flavors. You can move a home directory from an Ubuntu based tag to a Fedora based tag and user data carries over. + +### PUID and PGID + +The desktop user `abc` is remapped to these IDs at start, so files created in the `/config` volume belong to your host user. Find yours with `id`. One caveat: if you use the Docker in Docker feature, the Docker data itself runs as root and does not respect PUID and PGID. + +### shm_size + +Set `--shm-size=1gb` on everything. Browsers and Electron apps crash tabs with the Docker default of 64MB, and audio machinery also uses `/dev/shm`. It costs nothing when unused. + +### Timezone and language + +- `TZ` sets the timezone (e.g. `Europe/Amsterdam`). +- `LC_ALL` sets the session language, e.g. `LC_ALL=de_DE.UTF-8`. All locales are prebuilt in the images. See the [Configuration Reference](configuration.md#internationalization) for the common values. + +## Updating + +Standard container hygiene: pull the new image and recreate. + +```bash +docker compose pull && docker compose up -d +``` + +Because `/config` holds all user state, updates are non destructive. Tools like Watchtower or Diun work fine. + +## Special modes + +### Docker in Docker + +Run with `--privileged` and the container starts its own Docker daemon, letting the desktop user run containers inside the session (handy for development desktops). Mount `-v /path/to/docker:/var/lib/docker` for reasonable performance, and set `START_DOCKER=false` if you want privilege without the daemon. + +Alternatively mount the host socket, `-v /var/run/docker.sock:/var/run/docker.sock`, to control the host's Docker from inside the session. Understand the security implication: that is effectively root on the host. + +### Host networking + +Host networking is discouraged. These containers run a lot of internal services, and on the host network every one of them competes with your host and every other container for ports. Prefer normal bridged networking with the single published port. If you genuinely need `network_mode: host` (some VPN or discovery setups), it does work, use `CUSTOM_PORT`, `CUSTOM_HTTPS_PORT`, and `CUSTOM_WS_PORT` to move the internal ports out of the way of collisions. + +## Where next + +- [GPU Acceleration](gpu.md) for hardware rendering and encoding +- [Security and Hardening](security.md) before exposing anything +- [Reverse Proxy](reverse-proxy.md) for clean URLs and real TLS certificates diff --git a/docs/selkies/user-guide/installing-apps.md b/docs/selkies/user-guide/installing-apps.md new file mode 100644 index 00000000..18927e43 --- /dev/null +++ b/docs/selkies/user-guide/installing-apps.md @@ -0,0 +1,66 @@ +# Installing Applications + +The containers are immutable by design: anything installed with the system package manager lives in the container layer and disappears when the container is recreated (which happens on every image update). Only `/config`, the user's home directory, persists. + +There are two supported ways to add software, one persistent, one baked in at start. + +## PRoot Apps (persistent, recommended) + +[proot-apps](https://github.com/linuxserver/proot-apps) installs portable applications into the persistent `$HOME` directory, so they survive container upgrades and recreation. + +Inside the container (via the web terminal or `docker exec`): + +```bash +proot-apps install filezilla +``` + +The app appears in the session's menus and can be launched like any other application. Update and remove work the same way: + +```bash +proot-apps update filezilla +proot-apps remove filezilla +``` + +The list of supported applications is maintained in the [proot-apps README](https://github.com/linuxserver/proot-apps?tab=readme-ov-file#supported-apps). + +Two things to know: + +- Applications are mostly ingested from the Alpine repositories for maintainability, so what you get is the Alpine build of an app regardless of the flavor of the container you run it in. +- **Nvidia GPUs are not supported inside proot-apps.** If an app needs Nvidia acceleration, run it as its own dedicated image or bake it into a custom one. + +## Native packages via Docker mods (non persistent) + +For system packages, use the [universal-package-install](https://github.com/linuxserver/docker-mods/tree/universal-package-install) mod. Packages are installed by the init system every time the container starts: + +```yaml + environment: + - DOCKER_MODS=linuxserver/mods:universal-package-install + - INSTALL_PACKAGES=libfuse2|git|gdb +``` + +Trade offs: + +- Increases container start time on every boot. +- Not persistent in the image, but reinstalled automatically, so effectively stable as long as the variable stays set. +- Best for libraries and CLI tools an app needs, less ideal for large GUI applications. + +## Building your own image (permanent) + +If you always need the same software, the clean solution is a small downstream Dockerfile: + +```dockerfile +FROM lscr.io/linuxserver/webtop:ubuntu-kde + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git \ + build-essential && \ + apt-get autoclean && \ + rm -rf /var/lib/apt/lists/* /var/tmp/* /tmp/* +``` + +This is the beginning of the whole [Building Custom Images](../developer-guide/building-images.md) story in the Developer Guide. + +## A note on the App Lab + +If you are running [SealSkin](../components/sealskin.md), there is a fourth option: the App Lab lets you customize a base image interactively through the GUI, then commit the home directory as a reusable template, no Docker knowledge required. diff --git a/docs/selkies/user-guide/quickstart.md b/docs/selkies/user-guide/quickstart.md new file mode 100644 index 00000000..8a957574 --- /dev/null +++ b/docs/selkies/user-guide/quickstart.md @@ -0,0 +1,132 @@ +# Quickstart + +The single most important idea in this whole guide: **start minimal**. Almost every problem report we see involves a giant docker command assembled from years of forum posts. The containers are designed to work with almost nothing. + +## One command + +Pick an app (here Firefox) and run: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + lscr.io/linuxserver/firefox:latest bash +``` + +Open **https://localhost:3001** in your browser (note the `https`), accept the self signed certificate warning, and you are using Firefox running in a container. The trailing `bash` drops you into a shell inside the container, so when you are done, press `ctrl+d` and the container exits and cleans itself up. + +That is the entire quickstart. Everything else on this page and in this guide is an additive layer on top of that command. + +Want a full desktop instead of one app? + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +## Why these exact flags + +| Flag | Why | +| --- | --- | +| `--rm -it ... bash` | Throwaway foreground container with a shell inside it. You can poke around from the command line, and `ctrl+d` exits the shell and removes the container. Perfect for testing, switch to `-d` and a name for real deployments. | +| `--shm-size=1gb` | Browsers and Electron apps need more shared memory than the Docker default of 64MB. Without this, expect tab crashes. | +| `-p 3001:3001` | 3001 is the HTTPS port. HTTPS is required, the client uses modern browser APIs (WebCodecs) that only work in a secure context. Port 3000 serves plain HTTP for use behind a reverse proxy, do not browse to it directly. | + +## The debugging philosophy + +If a container misbehaves, do not add flags, remove them. Run the minimal command above for your app and see if the problem persists: + +```bash +docker run --rm -it --shm-size=1gb -p 3001:3001 lscr.io/linuxserver/firefox:latest bash +``` + +- **Works minimal but not in your setup?** Re-add your options one at a time (volumes, then env vars, then GPU flags) until it breaks. The last thing you added is the problem. +- **Broken even minimal?** Now you have a clean one line reproduction to include in a bug report, which is exactly what the maintainers will ask for. + +This loop solves the majority of issues, especially GPU issues, faster than anything else. + +## Adding the usual options + +A more realistic long term run, still deliberately small: + +```bash +docker run -d \ + --name=firefox \ + --shm-size=1gb \ + -e PUID=1000 \ + -e PGID=1000 \ + -e TZ=Etc/UTC \ + -p 3001:3001 \ + -v /path/to/config:/config \ + --restart unless-stopped \ + lscr.io/linuxserver/firefox:latest +``` + +- `PUID` and `PGID` map the in container user `abc` to your host user so files in the volume have sane ownership. +- `/config` is the persistent home directory. Everything the user saves, browser profiles, app settings, lives here and survives container recreation. + +Or as compose: + +```yaml +--- +services: + firefox: + image: lscr.io/linuxserver/firefox:latest + container_name: firefox + shm_size: 1gb + environment: + - PUID=1000 + - PGID=1000 + - TZ=Etc/UTC + volumes: + - /path/to/config:/config + ports: + - 3001:3001 + restart: unless-stopped +``` + +## Adding basic auth + +By default there is **no authentication**. On anything other than a trusted LAN, set at minimum: + +```bash + -e CUSTOM_USER=me \ + -e PASSWORD=changeme \ +``` + +This enables HTTP basic auth. It is fine for a home network. For anything internet facing, put the container behind a reverse proxy with real authentication, see [Security and Hardening](security.md). + +## Adding a GPU + +If the host has an Intel or AMD GPU with open source drivers, one flag gets you rendering and zero copy encoding on the card: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + --device /dev/dri \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +For Nvidia with the proprietary drivers: + +```bash +docker run --rm -it \ + --shm-size=1gb \ + -p 3001:3001 \ + --runtime nvidia \ + --gpus all \ + --device /dev/nvidia-modeset \ + lscr.io/linuxserver/webtop:ubuntu-kde bash +``` + +In both cases the GPU is detected and configured automatically. Nvidia has driver version prerequisites on the host, see [GPU Acceleration](gpu.md) for the full story including multi GPU selection and troubleshooting. + +## Where to next + +- Full install options and conventions: [Installation](installation.md) +- Everything the web client can do (files, clipboard, gamepads, sharing): [Using the Web Client](web-client.md) +- The complete list of apps and desktop flavors: [Available Apps and Desktops](apps.md) +- Locking things down: [Security and Hardening](security.md) diff --git a/docs/selkies/user-guide/reverse-proxy.md b/docs/selkies/user-guide/reverse-proxy.md new file mode 100644 index 00000000..8081a8d1 --- /dev/null +++ b/docs/selkies/user-guide/reverse-proxy.md @@ -0,0 +1,76 @@ +# Reverse Proxy + +Putting a Selkies container behind a reverse proxy gets you real TLS certificates, clean hostnames, and a place to hang serious authentication. The containers are designed for it. + +## The rules + +1. **Proxy to port 3000 (HTTP)**, and let your proxy terminate TLS. The container's port 3001 self signed HTTPS is for direct access; double TLS is pointless. +2. **WebSocket upgrades must pass through.** All streaming rides a WebSocket at `/websocket`. Any proxy that handles `Upgrade` and `Connection` headers works. +3. **Long timeouts.** Sessions are long lived connections. Set read and send timeouts to an hour (the internal Nginx uses 3600s), or idle sessions will drop. +4. **Client must still reach you over HTTPS.** The browser APIs need a secure context, so your proxy must serve HTTPS to the user. + +## SWAG + +[SWAG](https://github.com/linuxserver/docker-swag) ships preset configs for many of these containers. Enable the relevant proxy conf (for example `chromium.subdomain.conf.sample`), point it at your container, and you are done, including automatic Let's Encrypt certificates. SWAG is the path of least resistance and the one the LinuxServer team supports directly. + +## Plain Nginx example (subdomain) + +```nginx +server { + listen 443 ssl; + server_name desktop.example.org; + + # your ssl_certificate / ssl_certificate_key here + + location / { + proxy_pass http://:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_buffering off; + client_max_body_size 0; + } +} +``` + +`client_max_body_size` matters for file uploads into the session. + +## Subfolder proxying + +To serve a container under a path like `https://example.org/desktop/`, set the `SUBFOLDER` variable on the **container** so its internal routes match: + +```yaml + environment: + - SUBFOLDER=/desktop/ +``` + +Both slashes are required. Then proxy the location through: + +```nginx + location /desktop/ { + proxy_pass http://:3000/desktop/; + # same upgrade headers and timeouts as above + } +``` + +## Authentication at the proxy + +This is where real auth belongs for internet facing deployments. Common patterns: + +- **Authelia or Authentik** forward auth in front of the container location. +- **Basic auth at the proxy** as a floor, still better managed there than in the container. +- Keep the container's own `CUSTOM_USER` and `PASSWORD` as an inner layer if you like defense in depth; the proxy can inject the `Authorization` header upstream so users only log in once. + +If you are heading toward many users and many apps, that is exactly what [SealSkin](../components/sealskin.md) automates, including per session credentials and path based session routing. + +## Known sharp edges + +- The container substitutes `SUBFOLDER` and auth settings into its Nginx config with simple string replacement at startup. Exotic characters in passwords or paths can break the substitution, keep them simple. +- Server side events and the streaming WebSocket dislike buffering proxies, always disable response buffering (`proxy_buffering off` or your proxy's equivalent). +- If the client loads but you get a black screen or no video, it is almost always a blocked WebSocket upgrade or an HTTP (not HTTPS) page context. diff --git a/docs/selkies/user-guide/security.md b/docs/selkies/user-guide/security.md new file mode 100644 index 00000000..cc930d3e --- /dev/null +++ b/docs/selkies/user-guide/security.md @@ -0,0 +1,104 @@ +# Security and Hardening + +Read this page before exposing any Selkies container beyond your local machine. + +## The threat model in one paragraph + +!!! warning "This container provides privileged access to the host system" + The web interface includes a terminal with **passwordless sudo**. Any user who can reach the GUI can gain root inside the container, install arbitrary software, and probe your local network. Do not expose it to the internet unless you have secured it properly. + +Treat a Selkies session like an SSH login to a machine on your network, because functionally that is what it is. + +## HTTPS is required + +Modern browser features the client depends on, WebCodecs for video and audio in particular, only work in a secure context. That is why: + +- Port `3001` serves HTTPS with a self signed certificate and is the port you browse to directly. +- Port `3000` serves plain HTTP and exists **only** to sit behind a reverse proxy that terminates TLS. + +## Authentication layers + +**By default there is no authentication.** Your options, from weakest to strongest: + +1. **HTTP basic auth**: set `CUSTOM_USER` and `PASSWORD`. Adequate for a trusted home LAN, nothing more. +2. **Reverse proxy with real auth**: for anything internet facing, put the container behind a reverse proxy such as [SWAG](https://github.com/linuxserver/docker-swag) with a robust authentication mechanism (Authelia, Authentik, OAuth2 proxy, client certificates). See [Reverse Proxy](reverse-proxy.md). +3. **SealSkin**: if you are serving multiple users or apps, [SealSkin](../components/sealskin.md) handles authentication with public key cryptography and only proxies sessions to their owners. + +## Container isolation options + +- Avoid `--privileged` unless you specifically want the Docker in Docker feature. It dramatically widens the blast radius. +- Mounting `/var/run/docker.sock` hands the container control of the host Docker daemon, which is root on the host in practical terms. Only do this deliberately. +- `--security-opt seccomp=unconfined` disables a key Docker security layer. Use it only when a legacy host kernel or old libseccomp genuinely requires it, and treat it as a temporary workaround. + +## Hardening variables + +For kiosk deployments, single app terminals, classrooms, or any situation where the person at the keyboard is not the administrator, the baseimage ships lockdown variables. + +Note that the window manager level switches (`HARDEN_OPENBOX`, `DISABLE_CLOSE_BUTTON`, `DISABLE_MOUSE_BUTTONS`, `HARDEN_KEYBINDS`) only apply to **single application containers**, which run under labwc or Openbox. Full desktop Webtops manage their own windows, so only the desktop wide switches (`HARDEN_DESKTOP` and the `SELKIES_*` locks) matter there. The mechanics are documented in depth in [the developer guide](../developer-guide/building-images.md#the-window-manager-layer-labwc-and-openbox). + +### Umbrella switches + +| Variable | Description | +| --- | --- | +| `HARDEN_DESKTOP` | Enables `DISABLE_OPEN_TOOLS`, `DISABLE_SUDO`, and `DISABLE_TERMINALS`. Also sets the related client settings (`SELKIES_FILE_TRANSFERS`, `SELKIES_COMMAND_ENABLED`, `SELKIES_UI_SIDEBAR_SHOW_FILES`, `SELKIES_UI_SIDEBAR_SHOW_APPS`) unless you set them explicitly yourself | +| `HARDEN_OPENBOX` | Window manager lockdown for single app containers. Enables `DISABLE_CLOSE_BUTTON`, `DISABLE_MOUSE_BUTTONS`, and `HARDEN_KEYBINDS`, and flags `RESTART_APP` unless you set it, so the primary application restarts automatically if closed | + +### Individual switches + +| Variable | Description | +| --- | --- | +| `DISABLE_OPEN_TOOLS` | Disables the `xdg-open` and `exo-open` binaries by removing their execute permissions, so apps cannot spawn arbitrary helpers | +| `DISABLE_SUDO` | Disables `sudo` by removing execute permissions and invalidating the passwordless sudo configuration | +| `DISABLE_TERMINALS` | Disables common terminal emulators and hides them from the right click menu | +| `DISABLE_CLOSE_BUTTON` | Removes the close button from window title bars | +| `DISABLE_MOUSE_BUTTONS` | Disables right click and middle click context menus and actions in the window manager | +| `HARDEN_KEYBINDS` | Disables window manager keybinds that could bypass the other options, such as `alt+f4` to close windows or `alt+escape` for the root menu | +| `RESTART_APP` | Watchdog that restarts the main application if it is closed. The user's autostart script is made read only and root owned to prevent tampering | + +### Locking client settings + +Any boolean `SELKIES_*` setting can be pinned so the user cannot change it in the sidebar by appending `|locked`: + +```bash +-e SELKIES_CLIPBOARD_ENABLED="false|locked" +-e SELKIES_FILE_TRANSFERS="none" +-e SELKIES_UI_SHOW_SIDEBAR="false|locked" +``` + +See the [Configuration Reference](configuration.md) for the full list and value syntax. + +### Example: locked down kiosk browser + +```yaml +--- +services: + kiosk: + image: lscr.io/linuxserver/chromium:latest + shm_size: 1gb + environment: + - HARDEN_DESKTOP=true + - HARDEN_OPENBOX=true + - "SELKIES_CLIPBOARD_ENABLED=false|locked" + - SELKIES_FILE_TRANSFERS=none + - "SELKIES_UI_SHOW_SIDEBAR=false|locked" + - NO_DECOR=true + - "CHROME_CLI=--kiosk https://youtube.com" + ports: + - 3001:3001 + restart: unless-stopped +``` + +Locking the sidebar off entirely (`SELKIES_UI_SHOW_SIDEBAR=false|locked`) is the finishing touch for a true kiosk, the visitor sees nothing but the app. + +## Sharing links + +The sharing feature generates links that grant access to the running session: full collaboration, view only, or gamepad only for players 2 through 4. The built in links are a **demo of the multi user protocol**, they carry no authentication of their own. Proper shared sessions need the token based access control an orchestrator like [SealSkin](../components/sealskin.md) wires up. Remember that anyone with a collaboration link has the same power as the primary user, including that passwordless sudo terminal. Disable what you do not need: + +```bash +-e SELKIES_ENABLE_SHARING=false # master switch +-e SELKIES_ENABLE_COLLAB=false # or individually +``` + +## Pelorus and the computer use API + +Enabling `PELORUS=true` starts an agent control API inside the container and forces the accessibility bridge on for all applications, which means any process in the session can read the full UI text of every app. The Pelorus API itself has no authentication and relies on the container's Nginx and network isolation. Treat a Pelorus enabled container as fully readable and controllable by anything that can reach it on its internal port, and never publish its ports directly. Details on the [Pelorus](../components/pelorus.md) page. diff --git a/docs/selkies/user-guide/troubleshooting.md b/docs/selkies/user-guide/troubleshooting.md new file mode 100644 index 00000000..228607d9 --- /dev/null +++ b/docs/selkies/user-guide/troubleshooting.md @@ -0,0 +1,64 @@ +# Troubleshooting + +## Step zero: the minimal command + +Before anything else, reduce to the minimal command for your image: + +```bash +docker run --rm -it --shm-size=1gb -p 3001:3001 lscr.io/linuxserver/:latest bash +``` + +The trailing `bash` gives you a shell in the container for poking around, `ctrl+d` exits and cleans up. + +and browse to `https://localhost:3001` from the Docker host if possible. + +- **Works?** Your problem is in your added options. Re-add them one at a time: volume, env vars, GPU flags, proxy. The last addition before it breaks is your culprit. +- **Still broken?** You now have a one line reproduction. Grab the container logs and open an issue on the app's repository, this is exactly the report maintainers want. + +This loop resolves the majority of reports, particularly GPU and reverse proxy issues. + +## Quick reference table + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Page loads, no video, or "insecure context" errors | Browsing plain HTTP | Use `https://host:3001`, or fix TLS at your proxy. WebCodecs requires a secure context | +| Black screen behind the UI | WebSocket blocked | Check reverse proxy `Upgrade` headers and timeouts, see [Reverse Proxy](reverse-proxy.md) | +| Applications crash inside the session | Shared memory too small | `--shm-size=1gb` | +| Container restarts or app crashes on old hardware or NAS | Old kernel or libseccomp blocking syscalls | Try `--security-opt seccomp=unconfined` (understand the [security cost](security.md#container-isolation-options)) | +| Blurry colored text | 4:2:0 chroma subsampling | Enable FullColor 4:4:4 in the sidebar or use the JPEG encoder, note the [Intel and AMD caveat](gpu.md#fullcolor-444-and-hardware-encoders) | +| Choppy video during motion | CPU limited or bandwidth limited | Check the Stats section, lower FPS or raise CRF, try Turbo mode off, consider a [GPU](gpu.md) | +| Cursor feels laggy in games | Absolute pointer mode | Use Gaming mode (pointer lock) | +| Gamepad not detected in app | App does not use the joystick API path | Some apps are incompatible with the userspace interposer; try another input mode in app, or file an issue | +| Files will not upload | Proxy body size limit | Raise `client_max_body_size` at your proxy | +| Auth prompt not appearing | `PASSWORD` unset | Both `CUSTOM_USER` and `PASSWORD` behavior: no password means no auth | +| Volume permission errors in `/config` | PUID and PGID mismatch | Set them to your host user's `id` values | + +## GPU problems + +Work through the ladder in [GPU Acceleration](gpu.md#debugging-gpu-problems). Summary: + +1. Confirm the container works with no GPU flags at all. +2. Add only the device mount and watch the startup logs for detection messages. +3. Verify inside the container: run `vkcube` in a session terminal, a smoothly spinning cube and the GPU listed in the terminal means the GPU path works. `vainfo` for Intel and AMD and `nvidia-smi` for Nvidia confirm device visibility. +4. Nvidia specifics: driver installed from the `.run` file, `--device /dev/nvidia-modeset` for Vulkan (drivers 595.80 and newer need nothing else), kernel parameters `nvidia-drm.modeset=1 nvidia_drm.fbdev=1` on drivers below 595.80, and no Nvidia support on Alpine images. +5. Multi GPU: pin `DRINODE` and `DRI_NODE` explicitly, remember they must match for zero copy. + +## Reading the logs + +```bash +docker logs -f +``` + +The startup sequence prints the s6 service initialization, GPU detection results, and the mode selection (Wayland vs X11). The pixelflux layer logs its encoding decision, look for lines telling you whether the zero copy path or a readback path was chosen, and which encoder is active. + +For deeper debugging set `-e SELKIES_DEBUG=true`. + +## Performance expectations + +Calibrate before assuming a bug: a CPU only x86_64 host from the last decade comfortably serves a 1080p60 desktop session, and a budget N97 class mini PC has demonstrated eight simultaneous Firefox sessions running youtube. If you are far from that, the Stats sidebar (bandwidth, latency, FPS) will point at the bottleneck: network jitter shows up as latency spikes, CPU saturation as low server FPS, decode issues as low client FPS with fine server FPS. + +## Getting help + +- Each app image has its own issue tracker at `github.com/linuxserver/docker-`. +- Platform wide discussion happens on the [LinuxServer.io Discord](https://linuxserver.io/discord) and forums. +- Include: the minimal command you tested, full container logs, host OS and kernel, browser and version, and GPU details if relevant. diff --git a/docs/selkies/user-guide/web-client.md b/docs/selkies/user-guide/web-client.md new file mode 100644 index 00000000..29b551ae --- /dev/null +++ b/docs/selkies/user-guide/web-client.md @@ -0,0 +1,89 @@ +# Using the Web Client + +The web client is a complete workstation interface, not just a video player. This page tours everything it can do. Nearly all of it can be hidden or locked by the administrator with the `SELKIES_UI_*` variables in the [Configuration Reference](configuration.md). + +## The sidebar + +Toggle the sidebar with its handle on screen. At the top you get the core toggles: display, audio, microphone, and gamepad on or off, plus buttons for fullscreen, virtual trackpad, gaming mode, and the on screen keyboard. + +## Video settings + +- **Encoder**: H.264 (`x264enc`), striped H.264, or JPEG. H.264 is the default and right for everything modern, with FullColor 4:4:4 plus paint over it is visually equal to lossless stills. JPEG exists for unsupported browsers that cannot decode video frames at all. +- **Frames per second** and **CRF** (H.264 quality, lower is better) sliders, plus separate paint over quality controls. "Paint over" is the platform's signature feature: after the screen goes still, it is repainted at high quality so text stays crisp. +- **FullColor 4:4:4**: true 8 bit color with no chroma subsampling. Fixes blurry colored text. Note the [GPU caveats](gpu.md#fullcolor-444-and-hardware-encoders). +- **Turbo mode** (streaming mode): disables all the damage tracking logic and encodes every frame like a traditional video stream. Can be useful for gaming and full motion video. +- **CPU encoding** toggle to force cpu encoding. + +!!! tip "On a slow machine or a slow link, turn the stream down" + The defaults favor fluidity. If your client device is low end or your bandwidth is poor, reduce **Frames per second** first and raise **CRF** if needed (higher CRF means smaller frames). Paint over still keeps static content sharp, so a lower FPS and quality setting costs far less than you would expect for desktop work. + +## Screen settings + +- **Resolution**: by default the remote resolution follows your browser window exactly. You can instead pick a preset (720p through 4K) or type a manual width and height. +- **UI scaling (DPI)** for HiDPI displays, with a choice between pixel perfect HiDPI rendering and CSS scaling (lighter on bandwidth, softer image). +- **Scale locally** stretches a fixed remote resolution to fit your window. +- **Add Screen +**: opens a second browser window that becomes a second monitor for the session, positioned left, right, above, or below the primary. Arrange each window on the matching physical monitor and you have a real dual screen remote desktop. (Second screen is currently a feature of the X11 stack; in Wayland mode it is disabled.) + +## Audio and microphone + +Opus audio streams from the session to your browser, and output device selection if your browser exposes multiple sinks. The microphone button forwards your local mic into the session, where apps see it as a normal input device, video calls from inside a remote browser container work. + +## Clipboard + +Bidirectional clipboard sync between your machine and the session, automatic in both directions for text. The sidebar shows an editable view of the server clipboard. Enabling **Image support** (binary clipboard, `SELKIES_ENABLE_BINARY_CLIPBOARD`) adds images and other binary formats. Clipboard direction and availability can be locked down by the admin. + +## Files + +- **Upload**: drag and drop files anywhere on the session window, or use the Upload button. Files land in the session's `~/Desktop` by default (configurable with `FILE_MANAGER_PATH`). +- **Download**: the Files section opens a dark themed file index of the same directory served by the container's Nginx, click to download. + +Transfers can be restricted per direction or disabled entirely with `SELKIES_FILE_TRANSFERS`. + +## Gamepads + +Plug and play controller support for PlayStation, Xbox, and Switch pads, with mappings derived from the SDL game controller database. Inside the session, apps see a standard Xbox 360 style device provided by a userspace interposer, no kernel modules or privileged devices involved. Up to four controllers are supported, and the sharing feature can hand player 2 through 4 slots to remote friends. A configurable **touch gamepad** overlay provides on screen controls for phones and tablets. + +## Sharing and collaboration + +The Sharing section generates links for other people to join your running session: + +| Link | Capability | +| --- | --- | +| Collaboration | Full interactive control alongside you | +| View only | Watch the session, no input | +| Player 2, 3, 4 | Gamepad input only, for couch co-op over the internet | + +Anyone with a collaboration link has full control, treat these links like credentials. Admins can disable any of them, see [Security](security.md#sharing-links). + +!!! warning "The built in sharing links are a demo" + Out of the box these links carry no real authentication of their own, they are a demonstration of the underlying multi user protocol. For proper shared sessions, the token based access control that [SealSkin](../components/sealskin.md) wires up is the intended path: named participants, revocable access, and per user permissions. Use the raw links only on networks you trust. + +## Inside a single app container + +Full desktop Webtops behave like any desktop. Single app containers (Firefox, Chromium, and friends) instead run a minimal window manager, labwc on Wayland or Openbox on X11, with no panel or taskbar. Three gestures cover everything you need: + +- **Right click the desktop** (any empty area behind or beside the app) to open the root menu, which lets you launch a terminal or other bundled tools. +- **Middle click the desktop** to see and restore minimized windows. +- `ctrl+shift+d` toggles window decorations if you need to move or resize the app window. + +If these menus seem missing, the administrator has likely [hardened](security.md) the container, that is a feature, not a bug. + +## Mobile and touch + +On phones and tablets the client offers: + +- **Trackpad mode**: the screen becomes a laptop style trackpad with relative cursor movement, tap to click, tap and hold to drag, and two finger scrolling. Ideal for desktop apps on a phone. +- **Direct touch mode**: taps map straight to absolute clicks, best for touch friendly apps. +- The **on screen keyboard** button raises your device keyboard, with IME input (for example Chinese Pinyin) supported end to end. + +## Gaming mode + +Gaming mode grabs the pointer (pointer lock) and sends relative mouse movement for accurate FPS style aiming, pairs well with Turbo encoding mode and gamepads. + +## Stats + +Live overlay of client FPS, round trip latency, bandwidth, video bitrate, CPU, memory, and GPU utilization of the server. Your first stop when something feels off, before diving into [Troubleshooting](troubleshooting.md). + +## Apps section + +A management panel for installing additional applications into the session (backed by the proot-apps ecosystem described in [Installing Applications](installing-apps.md)). Admins commonly hide this in locked down deployments. diff --git a/mkdocs.yml b/mkdocs.yml index f20b2f0e..635b4c19 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,7 +75,11 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - tables - toc: permalink: true