From a1f0aa88c6a60d44e09ef41839c09985ec7b18fe Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 19:33:00 +0000 Subject: [PATCH 01/14] refactor(webtop): drop nginx, bind selkies directly on port 3000 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selkies already speaks WebSocket natively — the nginx reverse proxy was just adding indirection. Bind selkies to 0.0.0.0:3000 directly, removing nginx as a dependency entirely. Changes: - selkies-native.sh: remove nginx start/stop/status, remove config template deployment, change SELKIES_ADDR default to 0.0.0.0, SELKIES_PORT default to 3000. Add Rust toolchain + build deps install (nasm, cmake, libudev-dev, etc.) since pixelflux/pcmflux require them. Simplify install to build all three from git directly. - SKILL.md: remove nginx from prerequisites, commands, procedure, pitfalls, security, files tree. Update architecture diagram, config table, code examples to reflect direct selkies on port 3000. - troubleshooting.md: remove nginx entries, update port references from 8082 to 3000. - Delete templates/nginx.conf.template (no longer needed). --- .../skills/codespace-webtop/SKILL.md | 81 ++++++--- .../references/troubleshooting.md | 56 ++++-- .../scripts/selkies-native.sh | 159 +++++------------- .../templates/nginx.conf.template | 21 --- 4 files changed, 151 insertions(+), 166 deletions(-) delete mode 100644 .devcontainer/skills/codespace-webtop/templates/nginx.conf.template diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index 9788fcc..9f2c32f 100644 --- a/.devcontainer/skills/codespace-webtop/SKILL.md +++ b/.devcontainer/skills/codespace-webtop/SKILL.md @@ -20,7 +20,7 @@ Matches what `linuxserver/webtop:ubuntu-xfce` provides in Docker, but runs nativ ## Architecture ``` -Browser (port 3000) → nginx → selkies (127.0.0.1:8082, mode=websockets) +Browser (port 3000) → selkies (0.0.0.0:3000, mode=websockets) selkies drives pixelflux capture on Xvfb :20 running XFCE pixelflux: Rust X11 capture → H.264/JPEG stripes → WebSocket ``` @@ -34,17 +34,50 @@ Browser (port 3000) → nginx → selkies (127.0.0.1:8082, mode=websockets) ## Prerequisites +### Runtime deps (needed by selkies at runtime) + | Component | Purpose | Check | |-----------|---------|-------| | `ubuntu` / `debian` base | apt package manager | `lsb_release -is` | | `sudo` | install system deps | `sudo -n true` | | `python3` + `pip3` | selkies venv | `python3 --version` | -| `nginx` | reverse proxy + WS upgrade | `nginx -v` | | `Xvfb` | headless X11 server | `Xvfb -version` | | `xfce4` + `xfce4-goodies` | desktop environment | `xfce4-session --version` | | `dbus-x11` | session bus | `dbus-daemon --version` | | `libva2 libva-drm2 libva-x11-2` | H.264 encoding (pixelflux) | `dpkg -l libva2` | +### Build deps (required to build pixelflux + pcmflux from git source) + +The source install (the only reliable path) compiles Rust extensions (pixelflux, pcmflux) that link against system C libraries. **Why git source?** selkies `main` branch requires `pixelflux~=2.1.0` and `pcmflux~=2.1.0`, but these versions are unreleased — PyPI only has up to 2.0.0. The 2.1.0 versions exist in the selkies-project forks' git HEAD (their pyproject.toml says 2.1.0) but were never tagged or published. Building from git is the only way to satisfy these deps. These must be present **before** `pip install`: + +| Component | Purpose | Install | +|-----------|---------|--------| +| `rustc` + `cargo` | Rust compiler for pixelflux/pcmflux | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh -s -- -y` | +| `nasm` | x264 SIMD assembly (pixelflux GPL encoder) | `sudo apt-get install -y nasm` | +| `cmake` | builds turbojpeg-sys, x264-sys from source | `sudo apt-get install -y cmake` | +| `pkg-config` | finds system .pc files during cargo build | `sudo apt-get install -y pkg-config` | +| `libudev-dev` | libudev-sys Rust crate build | `sudo apt-get install -y libudev-dev` | +| `libx264-dev` | x264-sys links against system libx264 | `sudo apt-get install -y libx264-dev` | +| `libturbojpeg0-dev` | turbojpeg-sys links against system libturbojpeg | `sudo apt-get install -y libturbojpeg0-dev` | +| `libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev` | ffmpeg-sys-next links against system ffmpeg | `sudo apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev` | +| `libgbm-dev` | gbm-sys Rust crate build | `sudo apt-get install -y libgbm-dev` | +| `libinput-dev` | input-sys Rust crate build | `sudo apt-get install -y libinput-dev` | +| `libwayland-dev libxkbcommon-dev` | wayland crate builds | `sudo apt-get install -y libwayland-dev libxkbcommon-dev` | +| `libegl-dev libgles-dev` | EGL/GLES for smithay renderer | `sudo apt-get install -y libegl-dev libgles-dev` | +| `libclang-dev` | bindgen (Rust FFI generator) | `sudo apt-get install -y libclang-dev` | + +**One-liner to install all build deps**: +```bash +# Rust toolchain +export RUSTUP_HOME=~/.rustup CARGO_HOME=~/.cargo +source ~/.cargo/env 2>/dev/null || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source ~/.cargo/env +# System build deps +sudo apt-get install -y nasm cmake pkg-config libudev-dev libx264-dev \ + libturbojpeg0-dev libavcodec-dev libavformat-dev libavutil-dev \ + libswscale-dev libavfilter-dev libgbm-dev libinput-dev \ + libwayland-dev libxkbcommon-dev libegl-dev libgles-dev libclang-dev +``` + ## Quick Start ```bash @@ -67,13 +100,13 @@ Browser (port 3000) → nginx → selkies (127.0.0.1:8082, mode=websockets) | Command | Description | |---------|-------------| | `prereqs [--fix]` | Check (and optionally install) system dependencies | -| `install` | Create venv, install selkies wheel + deps, deploy nginx config | -| `start` | Xvfb → XFCE → selkies → nginx (all via PID tracking) | +| `install` | Create venv, install selkies+pixelflux+pcmflux from git, build web client | +| `start` | Xvfb → XFCE → selkies (all via PID tracking) | | `stop` | Clean shutdown all components | | `restart` | stop + start | | `status` | Show PID/health of each component | | `autostart enable\|disable\|status` | Wire/remove hook into `~/.bashrc` or systemd | -| `logs [component\|all]` | Tail logs (xvfb, xfce, selkies, nginx) | +| `logs [component\|all]` | Tail logs (xvfb, xfce, selkies) | ## Procedure @@ -92,13 +125,19 @@ Validates: OS, sudo, python3, pip3, and all apt packages listed above. ### 2. Install (`install`) 1. Creates venv at `~/.selkies/venv` (or `$SELKIES_VENV_DIR`) -2. Acquires the pixelflux-based `selkies` wheel. **Order matters** — none of these are vendored, all are obtained at install time: - - **(a)** Use a vendored `selkies-0.0.0.dev0-py3-none-any.whl` if present in `wheels/` - - **(b)** Try `cmd_download_wheel` (GitHub Actions `selkies-wheel` artifact) — NOTE this needs auth and usually 401s, so treat as best-effort - - **(c)** **Reliable fallback: build from git source** — `pip wheel git+https://github.com/selkies-project/selkies.git`. This is the only path that works unattended on a fresh Codespace. (PyPI `selkies==1.6.1` is the WRONG legacy GStreamer package; the `releases/latest` wheel URL is dead.) +2. Installs pixelflux, pcmflux, and selkies from git source. **Why git source?** selkies `main` branch requires `pixelflux~=2.1.0` and `pcmflux~=2.1.0`, which are unreleased — PyPI only has up to 2.0.0. The 2.1.0 versions exist only in git HEAD. Building from git is the only way to satisfy these deps: + ```bash + source ~/.selkies/venv/bin/activate && source ~/.cargo/env + pip install "git+https://github.com/selkies-project/pixelflux.git" + pip install "git+https://github.com/selkies-project/pcmflux.git" + pip install "git+https://github.com/selkies-project/selkies.git" + ``` + (PyPI `selkies==1.6.1` is the WRONG legacy GStreamer package.) 3. **Build the web client** (`cmd_build_web`): clone the full selkies repo (both `addons/selkies-web-core` and `addons/selkies-dashboard` must be siblings), `npm install` + `npm run build` **selkies-web-core first** (the dashboard's prebuild imports its `dist/selkies-core.js`), then build **selkies-dashboard**, and copy `addons/selkies-dashboard/dist/` → `~/.selkies/web_root`. The web client is **NOT** bundled in the wheel — serve the dashboard, NOT bare web-core (see Pitfalls: bare core = no sidebar). -4. Deploys nginx config (template with placeholder substitution) to `/etc/nginx/sites-enabled/selkies` -5. Validates `nginx -t` + + **Vite build workaround**: The Hermes terminal tool may detect `npx vite build` or `./node_modules/.bin/vite` as a long-lived server and refuse to run it. Use `node ./node_modules/vite/bin/vite.js build` instead. + + **Dashboard prebuild**: `npm run build` in selkies-dashboard runs `node copy-core.js` as a prebuild hook. If using direct `node` invocation for vite, run `node copy-core.js` manually before the vite build. **Note**: The skill no longer ships vendored wheels in `scripts/wheels/` (they're built at install time). A `.gitignore` in `scripts/` keeps any local wheels out of git. @@ -108,9 +147,8 @@ Order of operations: 1. **Xvfb :20** — `Xvfb :20 -screen 0 1920x1080x24 -nolisten tcp` 2. **XFCE session** — `DISPLAY=:20 dbus-launch --exit-with-session xfce4-session` - Auto-creates `~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml` from system default if missing (prevents "unable to load failsafe session") -3. **selkies** — `DISPLAY=:20 selkies --addr=127.0.0.1 --port=8082 --mode=websockets --web-root=~/.selkies/web_root --enable-basic-auth=false --framerate=30` - - `--web-root` MUST point at the built web client (`~/.selkies/web_root`, produced by `cmd_build_web` at install). Without it selkies returns **404 on `/`** and the proxy has no UI to serve. -4. **nginx** — `nginx` (proxies 3000 → 127.0.0.1:8082 with WS upgrade) +3. **selkies** — `DISPLAY=:20 selkies --addr=0.0.0.0 --port=3000 --mode=websockets --web-root=~/.selkies/web_root --enable-basic-auth=false --framerate=30` + - `--web-root` MUST point at the built web client (`~/.selkies/web_root`, produced by `cmd_build_web` at install). Without it selkies returns **404 on `/`**. Each component tracked via PID file in `/tmp/selkies-pids/`. @@ -127,13 +165,11 @@ Environment variables (all optional, with defaults): | Variable | Default | Description | |----------|---------|-------------| | `SELKIES_VENV_DIR` | `~/.selkies/venv` | Python venv location | -| `SELKIES_PORT` | `8082` | selkies internal port | -| `SELKIES_ADDR` | `127.0.0.1` | selkies bind address | -| `NGINX_PORT` | `3000` | nginx public port | +| `SELKIES_PORT` | `3000` | Port for selkies | +| `SELKIES_ADDR` | `0.0.0.0` | Bind address | | `XVFB_DISPLAY` | `:20` | Xvfb display number | | `XVFB_SCREEN` | `1920x1080x24` | Screen resolution | | `SELKIES_FRAMERATE` | `30` | Capture framerate | -| `SELKIES_WHEEL_URL` | (vendored) | URL to download selkies wheel if not local | ## Pitfalls @@ -142,12 +178,14 @@ Environment variables (all optional, with defaults): - **XFCE failsafe session popup**: XFCE needs `~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml`. Auto-created from system default on first start. - **DISPLAY not propagated**: `dbus-launch` loses `DISPLAY` unless explicitly exported: `env DISPLAY=:20 dbus-launch ...` - **WebSocket path**: selkies serves WS at `/api/websockets` (NOT `/websockets/primary` which 404s). -- **Port conflicts**: Default ports 8082 (selkies) and 3000 (nginx) must be free. +- **Port conflict**: Default port 3000 must be free. - **CI lint gate (repo `.devcontainer/skills/**`)**: Before committing any change to this skill, run `bash .devcontainer/skills/ci-lint-check/scripts/ci_lint_check.sh`. Markdown URLs MUST be wrapped in angle brackets `` or they fail `MD034/no-bare-urls` (this broke the initial PR #3 — two bare URLs in a References block). -- **selkies wheel acquisition is NOT plug-and-play**: The GitHub Actions artifact (`selkies-wheel`) requires auth (401 unauthenticated), the `releases/latest/download/selkies-wheel.zip` URL returns 404, and the PyPI package (`selkies==1.6.1`) is the wrong legacy GStreamer package. **The only reliable unattended path is `pip wheel git+https://github.com/selkies-project/selkies.git`** (built into `cmd_install` as fallback (c)). If you add a vendored wheel to `wheels/` it will be used, but the skill no longer ships one. +- **selkies wheel acquisition is NOT plug-and-play**: The GitHub Actions artifact (`selkies-wheel`) requires auth (401 unauthenticated), the `releases/latest/download/selkies-wheel.zip` URL returns 404, and the PyPI package (`selkies==1.6.1`) is the wrong legacy GStreamer package. **The only reliable unattended path is installing all three from git** (built into `cmd_install`). - **Web client MUST be `selkies-dashboard`, not `selkies-web-core` (this is the #1 failure mode).** The selkies repo has TWO web addons: `addons/selkies-web-core` (the embeddable streaming **Core** only, README literally says "for an external dashboard to interact with the client") and `addons/selkies-dashboard` (the standalone UI that embeds the Core). If `install` serves `selkies-web-core`, the webtop WILL launch and the desktop WILL render, but there is **NO sidebar** — the Core mounts with `_isSidebarOpen = !1` (closed) and only opens on a `toggleDashboard` window.postMessage. Symptom when this is wrong: video/audio/clipboard work fine, WS handshake returns `MODE websockets`, HTTP 200 — but the Selkies sidebar chrome (settings/stats/clipboard/files/shortcuts) is absent. Fix: build `selkies-dashboard` (its `prebuild` copies `../selkies-web-core/dist/selkies-core.js`, so both addons must be siblings under one cloned repo), then point `--web-root` at the dashboard's `dist/`. Verify by grepping the served bundle for `sidebar`/`toggle` (dashboard has ~90 `sidebar` + ~100 `toggle`; bare core has almost none and starts closed). See `references/web-client-no-sidebar.md` for the full diagnosis recipe. - **`autostart enable` must keep `{ ...; }` with a trailing `;`**: the appended rc line is `[[ -x ... ]] && { "$SCRIPT_DIR/selkies-native.sh" start; } &>/dev/null &`. A missing `;` before `}` (i.e. `{ ... start }`) makes bash throw `syntax error: unexpected end of file` for EVERY new login shell — it silently breaks `.bashrc` and anything that sources it. The template and script both use the `;`-terminated form; if you ever regenerate the hook by hand, keep it. - **`npm ci` fails on a fresh shallow clone**: `git clone --depth 1` may not ship a `package-lock.json` that `npm ci` requires, so `npm ci` errors and the build silently produces nothing (then the dashboard's `prebuild` aborts with "missing selkies-core.js"). Always wrap as `npm ci ... || npm install ...`, and build `selkies-web-core` BEFORE `selkies-dashboard` — dashboard's `prebuild` (`copy-core.js`) exits 1 if `../selkies-web-core/dist/selkies-core.js` is absent. +- **Interrupted apt install leaves dpkg broken**: Installing XFCE4 (a large package set) frequently hits the terminal timeout. When `prereqs --fix` fails mid-apt, `dpkg` is left in an unconfigured state and subsequent apt calls fail with "dpkg was interrupted". Fix: `sudo DEBIAN_FRONTEND=noninteractive dpkg --configure -a` before retrying. The `DEBIAN_FRONTEND=noninteractive` is critical — without it, `keyboard-configuration` launches an interactive debconf dialog that hangs the terminal. +- **pcmflux~=2.1.0 is a hidden dependency**: selkies requires `pcmflux~=2.1.0` (audio capture), but like pixelflux, 2.1.0 is unreleased on PyPI (max: 2.0.0). The git-source install must build pixelflux and pcmflux **before** selkies — pip resolves all `~=2.1.0` constraints at once, but only if the packages are already installed in the venv. ## Security @@ -158,7 +196,7 @@ Environment variables (all optional, with defaults): **If you run this on a VM/bare-metal host:** - Keep the forwarded port **private**, or -- Put nginx behind an authenticating reverse proxy (e.g. Authelia, OAuth2 Proxy, Cloudflare Access), or +- Put selkies behind an authenticating reverse proxy (e.g. Authelia, OAuth2 Proxy, Cloudflare Access), or - Enable selkies basic-auth (note: a single shared credential — weak on its own; defense-in-depth only). Greptile flagged this as P1 on PR #41. We keep auth off by design for the Codespace case (already gated) and document the exposure for the VM case rather than flipping the default, which would break the frictionless Codespace flow. @@ -197,7 +235,6 @@ codespace-webtop/ │ ├── selkies-native.sh # Main control script (install/start/stop/restart/...) │ └── prereqs.sh # Prerequisites checker + auto-fix ├── templates/ -│ ├── nginx.conf.template # nginx proxy config (3000 → 8082, WS upgrade) │ └── autostart.bashrc # Shell hook snippet └── references/ ├── architecture.md # Detailed architecture diagram diff --git a/.devcontainer/skills/codespace-webtop/references/troubleshooting.md b/.devcontainer/skills/codespace-webtop/references/troubleshooting.md index 4100408..8a442c5 100644 --- a/.devcontainer/skills/codespace-webtop/references/troubleshooting.md +++ b/.devcontainer/skills/codespace-webtop/references/troubleshooting.md @@ -31,12 +31,12 @@ If you already have a wheel, drop it in `scripts/wheels/` and it will be used (t ## selkies serves 404 on `/` but WS connects -**Cause**: The web client is NOT bundled in the selkies wheel. selkies returns 404 on `/` unless `--web-root` points at a built client. nginx proxies `/` to selkies, so you get a 404 page in the browser even though the WebSocket at `/api/websockets` may work. +**Cause**: The web client is NOT bundled in the selkies wheel. selkies returns 404 on `/` unless `--web-root` points at a built client. **Fix**: 1. Build the client: `cmd_build_web` (clone selkies repo → `addons/selkies-web-core` → `npm ci` → `npm run build` → copy `dist/` to `~/.selkies/web_root`). 2. Ensure `start` passes `--web-root=~/.selkies/web_root` (it does by default). -3. Verify: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8082/` → should be `200`. +3. Verify: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/` → should be `200`. ## ImportError: libva/libva-drm/libva-x11 @@ -51,28 +51,65 @@ sudo apt-get install -y libva2 libva-drm2 libva-x11-2 **Cause**: The WebSocket path changed in newer selkies versions. -**Fix**: The correct path is `/api/websockets`, not `/websockets/primary`. Ensure nginx config proxies WebSocket upgrade to the right path (the template handles this correctly). +**Fix**: The correct path is `/api/websockets`, not `/websockets/primary`. Ensure the selkies `--web-root` is set correctly. -## Nginx not starting on port 3000 +## selkies can't start on port 3000 (port in use) -**Cause**: Port already in use, or Codespaces port visibility not set to `public`. +**Cause**: Another process is using port 3000. **Fix**: ```bash -sudo nginx -s stop # if nginx is already running sudo lsof -i :3000 # check what's using the port -# In Codespaces: set port 3000 to public in VS Code Ports panel +# Kill the process or change SELKIES_PORT ``` ## selkies can't see DISPLAY **Cause**: DISPLAY env var not set or incorrect. -**Fix**: selkies must be started with `DISPLAY=:20` env var: +**Fix**: selkies must be started with `DISPLAY=:20` env var (the script handles this automatically): ```bash -DISPLAY=:20 ~/.selkies/venv/bin/selkies --addr=127.0.0.1 --port=8082 --mode=websockets +DISPLAY=:20 ~/.selkies/venv/bin/selkies --addr=0.0.0.0 --port=3000 --mode=websockets ``` +## pixelflux build fails (No CMAKE_ASM_NASM_COMPILER / missing libudev / missing libavutil) + +**Cause**: pixelflux compiles Rust extensions that link against system C libraries. The error messages vary by missing dependency: +- `No CMAKE_ASM_NASM_COMPILER` → missing `nasm` (for x264 SIMD assembly) +- `Package libudev was not found` → missing `libudev-dev` +- `HINT: if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing libavutil.pc` → missing ffmpeg dev packages + +**Fix**: Install all build deps before `pip install`: +```bash +sudo apt-get install -y nasm cmake pkg-config libudev-dev libx264-dev \ + libturbojpeg0-dev libavcodec-dev libavformat-dev libavutil-dev \ + libswscale-dev libavfilter-dev libgbm-dev libinput-dev \ + libwayland-dev libxkbcommon-dev libegl-dev libgles-dev libclang-dev +``` +Plus Rust toolchain: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y` + +See the "Build deps" table in SKILL.md Prerequisites for the full list with purposes. + +## pcmflux~=2.1.0 not found (Could not find a version that satisfies the requirement) + +**Cause**: `selkies` requires `pcmflux~=2.1.0` (audio capture), but 2.1.0 is unreleased on PyPI (max: 2.0.0). Like pixelflux, the 2.1.0 version exists only in the selkies-project fork's git HEAD. Building from git is the only way to get it. + +**Fix**: Install pcmflux from git **before** selkies: +```bash +source ~/.selkies/venv/bin/activate +pip install "git+https://github.com/selkies-project/pcmflux.git" +``` + +## Interrupted apt leaves dpkg broken (dpkg was interrupted) + +**Cause**: Installing XFCE4 (a large package set) can hit the terminal timeout, leaving dpkg in an unconfigured state. All subsequent apt calls fail with "dpkg was interrupted". + +**Fix**: +```bash +sudo DEBIAN_FRONTEND=noninteractive dpkg --configure -a +``` +The `DEBIAN_FRONTEND=noninteractive` is critical — without it, `keyboard-configuration` launches an interactive debconf dialog that hangs the terminal. After this, retry `prereqs --fix`. + ## Process won't die after stop **Cause**: `setsid` spawns processes in a new session; PID tracking may miss child processes. @@ -84,7 +121,6 @@ DISPLAY=:20 ~/.selkies/venv/bin/selkies --addr=127.0.0.1 --port=8082 --mode=webs pkill -f "pixelflux" 2>/dev/null pkill -f "xfce4" 2>/dev/null pkill -f "Xvfb :20" 2>/dev/null -sudo nginx -s quit 2>/dev/null ``` ## XFCE components not starting diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 306cde1..6894e50 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -1,27 +1,21 @@ #!/usr/bin/env bash # selkies-native.sh — Native Selkies/XFCE webtop (browser desktop) control script -# Manages Xvfb → XFCE → selkies (pixelflux) → nginx stack +# Manages Xvfb → XFCE → selkies (pixelflux) stack # Generic: works on any Ubuntu/Debian base system (Codespaces, VM, bare metal) set -uo pipefail # ── Paths (override via env vars for portability) ────────────────────── SCRIPT_DIR="${SELKIES_SCRIPT_DIR:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)}" VENV_DIR="${SELKIES_VENV_DIR:-$HOME/.selkies/venv}" -WHEEL_DIR="${SELKIES_WHEEL_DIR:-$SCRIPT_DIR/wheels}" -SELKIES_WHEEL="${SELKIES_WHEEL:-$WHEEL_DIR/selkies-0.0.0.dev0-py3-none-any.whl}" -NGINX_TEMPLATE="${SELKIES_NGINX_TEMPLATE:-$SCRIPT_DIR/../templates/nginx.conf.template}" -NGINX_SITE="${NGINX_SITE:-/etc/nginx/sites-enabled/selkies}" PID_DIR="${SELKIES_PID_DIR:-/tmp/selkies-pids}" LOG_DIR="${SELKIES_LOG_DIR:-/tmp/selkies-logs}" -WHEEL_URL="${SELKIES_WHEEL_URL:-https://github.com/selkies-project/selkies/releases/latest/download/selkies-wheel.zip}" # Display & ports (override via env) XVFB_DISPLAY="${XVFB_DISPLAY:-:20}" XVFB_SCREEN="${XVFB_SCREEN:-1920x1080x24}" -SELKIES_ADDR="${SELKIES_ADDR:-127.0.0.1}" -SELKIES_PORT="${SELKIES_PORT:-8082}" +SELKIES_ADDR="${SELKIES_ADDR:-0.0.0.0}" +SELKIES_PORT="${SELKIES_PORT:-3000}" SELKIES_FRAMERATE="${SELKIES_FRAMERATE:-30}" -NGINX_PORT="${NGINX_PORT:-3000}" # Web client root (built by cmd_build_web during install) WEB_ROOT="${SELKIES_WEB_ROOT:-$HOME/.selkies/web_root}" @@ -118,6 +112,8 @@ status_daemon() { cmd_install() { echo "=== selkies-native: install ===" + export DEBIAN_FRONTEND=noninteractive + if ! sudo -n true 2>/dev/null; then echo "[install] WARNING: passwordless sudo not available." echo " Commands requiring root will prompt for password." @@ -127,67 +123,51 @@ cmd_install() { echo "[apt] updating package list..." sudo apt-get update -qq - echo "[apt] installing system dependencies..." + echo "[apt] installing runtime dependencies..." sudo apt-get install -y -qq \ xvfb xfce4 xfce4-goodies dbus-x11 \ - nginx python3-venv python3-pip \ + python3-venv python3-pip \ libva2 libva-drm2 libva-x11-2 \ curl 2>&1 | tail -5 + echo "[apt] installing build dependencies for pixelflux/pcmflux..." + sudo apt-get install -y -qq \ + nasm cmake pkg-config \ + libudev-dev libx264-dev libturbojpeg0-dev \ + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ + libgbm-dev libinput-dev libwayland-dev libxkbcommon-dev \ + libegl-dev libgles-dev libclang-dev libpixman-1-dev libdrm-dev \ + 2>&1 | tail -5 + + # Rust toolchain (required for pixelflux/pcmflux PyO3 builds) + if ! command -v cargo &>/dev/null; then + echo "[rust] installing Rust toolchain..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + else + echo "[rust] already installed ($(rustc --version))" + fi + # Virtualenv echo "[venv] creating at $VENV_DIR" python3 -m venv "$VENV_DIR" "$VENV_DIR/bin/pip" install --quiet --no-cache-dir --upgrade pip wheel - # Install selkies wheel (pixelflux + pcmflux pulled from PyPI as deps) - if [[ ! -f "$SELKIES_WHEEL" ]]; then - echo "[wheel] vendored wheel not found at $SELKIES_WHEEL" - cmd_download_wheel - fi - - # Reliable fallback: build the wheel from the selkies git source. + # Install selkies from git source. # PyPI selkies==1.6.1 is the legacy GStreamer package (wrong). - # The GitHub Actions selkies-wheel artifact needs auth (401) and the - # releases/latest download URL is dead, so building from git is the - # only path that works unattended on a fresh install. - if [[ ! -f "$SELKIES_WHEEL" ]]; then - echo "[wheel] download failed — building wheel from git source..." - "$VENV_DIR/bin/pip" wheel --no-cache-dir \ - --wheel-dir "$(dirname "$SELKIES_WHEEL")" \ - "git+https://github.com/selkies-project/selkies.git" 2>&1 | tail -5 - local built - built="$(find "$(dirname "$SELKIES_WHEEL")" -name 'selkies-*.whl' -print -quit 2>/dev/null)" - [[ -n "$built" ]] && SELKIES_WHEEL="$built" - fi - - if [[ ! -f "$SELKIES_WHEEL" ]]; then - echo "[wheel] ERROR: selkies wheel not available" - echo " See SKILL.md for manual build from git source" - return 1 - fi - - echo "[pip] installing pixelflux, pcmflux, and selkies wheel..." + # selkies main branch requires pixelflux~=2.1.0 and pcmflux~=2.1.0, + # which are unreleased on PyPI (max: 2.0.0). The 2.1.0 versions exist + # only in git HEAD. Build all three from git in order. + echo "[pip] installing pixelflux, pcmflux, and selkies from git..." "$VENV_DIR/bin/pip" install --no-cache-dir \ - pixelflux pcmflux \ - "$SELKIES_WHEEL" + "git+https://github.com/selkies-project/pixelflux.git" \ + "git+https://github.com/selkies-project/pcmflux.git" \ + "git+https://github.com/selkies-project/selkies.git" # Build and install selkies web frontend (selkies-dashboard + embedded core) - echo "[web] building selkies-dashboard web client (vite)..." + echo "[web] building selkies-dashboard web client..." cmd_build_web - # Install nginx config template (substitute placeholders) - echo "[nginx] installing config to $NGINX_SITE" - local tmp_conf - tmp_conf="$(mktemp /tmp/selkies-nginx-XXXXXX.conf)" - sed \ - -e "s/NGINX_PORT_PLACEHOLDER/$NGINX_PORT/" \ - -e "s/SELKIES_ADDR_PLACEHOLDER/$SELKIES_ADDR/" \ - -e "s/SELKIES_PORT_PLACEHOLDER/$SELKIES_PORT/" \ - "$NGINX_TEMPLATE" > "$tmp_conf" - sudo cp "$tmp_conf" "$NGINX_SITE" - sudo nginx -t >/dev/null 2>&1 || { echo "[nginx] config test failed"; return 1; } - rm -f "$tmp_conf" - # Mark as installed mkdir -p "$PID_DIR" touch "$PID_DIR/.installed" @@ -195,39 +175,6 @@ cmd_install() { echo "=== install complete ===" } -# Download selkies wheel from GitHub Actions artifact -cmd_download_wheel() { - if ! command -v curl >/dev/null 2>&1; then - echo "[wheel] curl required for download" - return 1 - fi - - echo "[wheel] downloading from GitHub Actions..." - local tmpzip="/tmp/selkies-wheel-$$.zip" - curl -sL "$WHEEL_URL" -o "$tmpzip" - if [[ ! -s "$tmpzip" ]]; then - echo "[wheel] download failed" - rm -f "$tmpzip" - return 1 - fi - - # Extract the wheel from the zip - local tmpdir="/tmp/selkies-extract-$$" - mkdir -p "$tmpdir" - unzip -o -q "$tmpzip" -d "$tmpdir" 2>/dev/null || true - local wheel="$(find "$tmpdir" -name 'selkies-*.whl' -print -quit 2>/dev/null)" - if [[ -n "$wheel" && -f "$wheel" ]]; then - mkdir -p "$(dirname "$SELKIES_WHEEL")" - cp "$wheel" "$SELKIES_WHEEL" - echo "[wheel] extracted to $SELKIES_WHEEL" - else - echo "[wheel] no wheel found in archive" - rm -rf "$tmpdir" "$tmpzip" - return 1 - fi - rm -rf "$tmpdir" "$tmpzip" -} - # Build selkies web frontend. # CRITICAL: serve selkies-dashboard, NOT selkies-web-core. # selkies-web-core is only the embeddable streaming Core: it mounts with the @@ -273,7 +220,7 @@ cmd_build_web() { ls -la "$web_dist/" } -# ── start: Xvfb → XFCE → selkies → nginx ─────────────────────────────── +# ── start: Xvfb → XFCE → selkies ───────────────────────────────────── cmd_start() { echo "=== selkies-native: start ===" @@ -323,9 +270,9 @@ cmd_start() { pkill -f "selkies.*--port=$SELKIES_PORT" 2>/dev/null || true sleep 0.5 # SECURITY: --enable-basic-auth=false is intentional. In a Codespace the - # public port (3000) is gated by GitHub's authenticated port-forward, so the + # public port is gated by GitHub's authenticated port-forward, so the # desktop is not exposed to the open internet. On a bare-metal/VM host where - # the port is publicly routed, keep it private or put nginx behind an + # the port is publicly routed, keep it private or put selkies behind an # authenticating proxy (selkies basic-auth is a single shared credential). DISPLAY="$XVFB_DISPLAY" start_daemon selkies \ "$VENV_DIR/bin/selkies" \ @@ -341,21 +288,15 @@ cmd_start() { # Wait for selkies HTTP local i=0 - while ! curl -s -o /dev/null -w "%{http_code}" "http://$SELKIES_ADDR:$SELKIES_PORT/" 2>/dev/null | grep -q "200\|302"; do + while ! curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$SELKIES_PORT/" 2>/dev/null | grep -q "200\|302"; do [[ $i -ge 30 ]] && { echo "[selkies] health check timeout"; return 1; } sleep 0.5 ((i++)) done echo "[selkies] HTTP ready" - # 4. nginx - echo "[nginx] starting on port $NGINX_PORT" - sudo nginx -t >/dev/null 2>&1 || { echo "[nginx] config test failed"; return 1; } - sudo nginx 2>&1 | grep -v "invalid PID" || true - sleep 1 - echo "=== selkies-native started ===" - echo " Access: forward port $NGINX_PORT → open in browser" + echo " Access: forward port $SELKIES_PORT → open in browser" } # Ensure XFCE session XML exists, copy from system default if missing @@ -435,8 +376,6 @@ cmd_stop() { stop_daemon selkies stop_daemon xfce stop_daemon xvfb - echo "[nginx] stopping" - sudo nginx -s quit 2>/dev/null || sudo pkill -f "nginx.*master" 2>/dev/null || true echo "=== selkies-native stopped ===" } @@ -453,15 +392,9 @@ cmd_status() { status_daemon xvfb || true status_daemon xfce || true status_daemon selkies || true - echo -n "[nginx] " - if sudo nginx -t >/dev/null 2>&1 && (ss -tlnp 2>/dev/null | grep -q ":$NGINX_PORT" || netstat -tlnp 2>/dev/null | grep -q ":$NGINX_PORT"); then - echo "RUNNING (port $NGINX_PORT)" - else - echo "STOPPED" - fi echo "" echo "Ports:" - ss -tlnp 2>/dev/null | grep -E ":($NGINX_PORT|$SELKIES_PORT)" 2>/dev/null || netstat -tlnp 2>/dev/null | grep -E ":($NGINX_PORT|$SELKIES_PORT)" 2>/dev/null || true + ss -tlnp 2>/dev/null | grep ":$SELKIES_PORT" 2>/dev/null || netstat -tlnp 2>/dev/null | grep ":$SELKIES_PORT" 2>/dev/null || true } # ── autostart: idempotent hook ──────────────────────────────────────── @@ -532,25 +465,25 @@ usage() { Usage: selkies-native.sh {install|start|stop|restart|status|autostart|logs} [args] Commands: - install Install system deps, create venv, install selkies+pixelflux+pcmflux, build web client, nginx config - start Start Xvfb → XFCE → selkies → nginx (selkies serves built web client via --web-root) + install Install system deps + Rust, create venv, install selkies+pixelflux+pcmflux from git, build web client + start Start Xvfb → XFCE → selkies (serves built web client via --web-root) stop Stop all components cleanly restart Stop then start status Show PID/health of each component autostart {enable|disable|status} — hook into shell rc file prereqs [--fix] Check (and optionally install) system dependencies - logs [component] Show tail of logs (xvfb/xfce/selbies/nginx or 'all') + logs [component] Show tail of logs (xvfb/xfce/selkies or 'all') Environment overrides: SELKIES_VENV_DIR Venv path (default: ~/.selkies/venv) - SELKIES_PORT selkies internal port (default: 8082) - NGINX_PORT Public port (default: 3000) + SELKIES_ADDR Bind address (default: 0.0.0.0) + SELKIES_PORT Port (default: 3000) SELKIES_WEB_ROOT Web client root dir (default: ~/.selkies/web_root, built at install) XVFB_DISPLAY X11 display (default: :20) XVFB_SCREEN Screen resolution (default: 1920x1080x24) Architecture: - Browser (port $NGINX_PORT) → nginx → selkies ($SELKIES_ADDR:$SELKIES_PORT, mode=websockets) + Browser (port $SELKIES_PORT) → selkies ($SELKIES_ADDR:$SELKIES_PORT, mode=websockets) selkies drives pixelflux capture on Xvfb $XVFB_DISPLAY running XFCE EOF } diff --git a/.devcontainer/skills/codespace-webtop/templates/nginx.conf.template b/.devcontainer/skills/codespace-webtop/templates/nginx.conf.template deleted file mode 100644 index 7ac2ab9..0000000 --- a/.devcontainer/skills/codespace-webtop/templates/nginx.conf.template +++ /dev/null @@ -1,21 +0,0 @@ -# nginx reverse proxy config for Selkies native desktop -# Proxies ALL traffic (HTTP + WebSocket) to selkies on 127.0.0.1:8082 -# selkies serves its own bundled React client HTML -server { - listen NGINX_PORT_PLACEHOLDER; - server_name _; - - # Proxy all HTTP traffic to selkies - location / { - proxy_pass http://SELKIES_ADDR_PLACEHOLDER:SELKIES_PORT_PLACEHOLDER; - 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 86400; - proxy_send_timeout 86400; - } -} \ No newline at end of file From 27aa324fe186208cef0d24672cbc201e2e679800 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 19:42:35 +0000 Subject: [PATCH 02/14] feat(webtop): clean up build-time artifacts after install to save ~2.3GB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After pixelflux/pcmflux compile their Rust extensions and the web client is built, remove build-time-only artifacts that the running webtop no longer needs: - ~/.rustup (1.5GB Rust toolchain) — only needed during pip install - ~/.cargo (612MB registry + build cache) — only needed during build - ~/.selkies/selkies-src (170MB web clone) — only dist/ matters - ~/.cache/pip (32MB) — download cache Only the compiled .so files in the venv + web_root dist are needed at runtime (~250MB), down from ~3GB after install. --- .../scripts/selkies-native.sh | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 6894e50..3cc439c 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -168,6 +168,28 @@ cmd_install() { echo "[web] building selkies-dashboard web client..." cmd_build_web + # ── Cleanup: remove build-time-only artifacts to save disk ────────── + # Rust toolchain + cargo registry are only needed during pip install + # (pixelflux/pcmflux compile Rust → .so). After that, only the + # compiled extensions in the venv are needed at runtime. + local before_kb after_kb saved_mb + before_kb=$(du -sk "$HOME/.rustup" "$HOME/.cargo" "$HOME/.selkies/selkies-src" "$HOME/.cache/pip" 2>/dev/null \ + | awk '{s+=$1} END{print s+0}') + + echo "[cleanup] removing Rust toolchain (build-time only, ~1.5GB)..." + rm -rf "$HOME/.rustup" "$HOME/.cargo" + + echo "[cleanup] removing selkies source clone (web dist already copied)..." + rm -rf "$HOME/.selkies/selkies-src" + + echo "[cleanup] removing pip cache..." + rm -rf "$HOME/.cache/pip" + + after_kb=$(du -sk "$HOME/.rustup" "$HOME/.cargo" "$HOME/.selkies/selkies-src" "$HOME/.cache/pip" 2>/dev/null \ + | awk '{s+=$1} END{print s+0}') + saved_mb=$(( (before_kb - after_kb) / 1024 )) + echo "[cleanup] freed ~${saved_mb}MB" + # Mark as installed mkdir -p "$PID_DIR" touch "$PID_DIR/.installed" From c08fc9cdf25e832821a218785f9866102b921234 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 19:58:25 +0000 Subject: [PATCH 03/14] fix(webtop): stop legacy nginx before starting selkies on port 3000 Greptile P1: existing installations with nginx still running on port 3000 would block selkies from binding. Add a guard at the top of cmd_start() that detects and gracefully stops any legacy nginx process before starting the new stack. --- .../skills/codespace-webtop/scripts/selkies-native.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 3cc439c..96abbd7 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -246,6 +246,14 @@ cmd_build_web() { cmd_start() { echo "=== selkies-native: start ===" + # 0. Kill legacy nginx if still running (from old skill installs). + # nginx used to proxy port 3000 → selkies; now selkies binds directly. + if pgrep -x nginx >/dev/null 2>&1; then + echo "[nginx] stopping legacy nginx process..." + sudo nginx -s quit 2>/dev/null || sudo pkill -x nginx 2>/dev/null || true + sleep 0.5 + fi + # 1. Xvfb echo "[Xvfb] starting on $XVFB_DISPLAY" local xvfb_pid="$(read_pid "$(pid_file xvfb)")" From 6eeaeec4668057baa4a1b0e139d51c11902ad213 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 20:11:01 +0000 Subject: [PATCH 04/14] docs: sync wiki articles and skill references with nginx removal Update wiki articles to reflect the new architecture where selkies binds directly to port 3000 (no nginx): - codespace-webtop.md: remove nginx from architecture diagram, key components table, and WebSocket endpoint section. Update package source and security sections. - codespace-port-visibility.md: remove nginx from integration example. - SKILL.md: update References section to say 'install from git source' instead of 'GitHub Actions selkies-wheel artifact'. --- .../skills/codespace-webtop/SKILL.md | 2 +- .../wiki/codespace-port-visibility.md | 1 - .devcontainer/wiki/codespace-webtop.md | 38 ++++--------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index 9f2c32f..e62eb5c 100644 --- a/.devcontainer/skills/codespace-webtop/SKILL.md +++ b/.devcontainer/skills/codespace-webtop/SKILL.md @@ -246,4 +246,4 @@ codespace-webtop/ - Architecture details: `references/architecture.md` - Troubleshooting: `references/troubleshooting.md` - PyPI selkies==1.6.1 is WRONG: (legacy GStreamer package) -- Correct pixelflux-based selkies: (GitHub Actions `selkies-wheel` artifact) +- Correct pixelflux-based selkies: (install from git source) diff --git a/.devcontainer/wiki/codespace-port-visibility.md b/.devcontainer/wiki/codespace-port-visibility.md index 5347d00..d2b31c6 100644 --- a/.devcontainer/wiki/codespace-port-visibility.md +++ b/.devcontainer/wiki/codespace-port-visibility.md @@ -108,7 +108,6 @@ python3 scripts/set_port_visibility.py 8080 public # 3. Start services LAVISH_AXI_NO_OPEN=1 node dist/cli.mjs sample.html --no-open & -sudo nginx -g 'daemon off;' & ``` ## Port Scheme Reference (Slot-based) diff --git a/.devcontainer/wiki/codespace-webtop.md b/.devcontainer/wiki/codespace-webtop.md index 1fd64b9..bc56928 100644 --- a/.devcontainer/wiki/codespace-webtop.md +++ b/.devcontainer/wiki/codespace-webtop.md @@ -15,13 +15,7 @@ The desktop is exposed to the browser over a **single WebSocket** (no WebRTC/UDP │ HTTPS/WS (single port) ▼ ┌─────────────────────────────────────────────────────────────┐ -│ nginx (0.0.0.0:3000) │ -│ - Proxies ALL traffic → 127.0.0.1:8082 │ -└──────────────────────────┬──────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ selkies server (127.0.0.1:8082) │ +│ selkies server (0.0.0.0:3000) │ │ - --mode=websockets (no WebRTC/UDP) │ │ - --enable-basic-auth=false (Codespaces handles auth) │ │ - --web-root=~/.selkies/web_root (built at install) │ @@ -42,9 +36,8 @@ The desktop is exposed to the browser over a **single WebSocket** (no WebRTC/UDP |-----------|---------|--------------|---------| | Xvfb | `Xvfb :20` | — | Headless X11 display (1920x1080x24) | | XFCE | `xfce4-session` | — | Desktop environment (window manager, panel, file manager) | -| selkies | `selkies` | 127.0.0.1:8082 | Serves React client via `--web-root` + WebSocket media/input protocol | +| selkies | `selkies` | 0.0.0.0:3000 | Serves React client via `--web-root` + WebSocket media/input protocol | | pixelflux | (Rust .so) | — | X11 screen capture → H.264/JPEG stripes | -| nginx | `nginx` | 0.0.0.0:3000 | Reverse proxy with WS upgrade | ## The Package Source Problem (Critical) @@ -61,15 +54,11 @@ The **correct pixelflux-based `selkies`** (v0.0.0.dev0) with: - WebSocket endpoint at `/api/websockets` - Console script `selkies` -...is distributed as a **GitHub Actions artifact** (`selkies-wheel`) from the `selkies-project/selkies` repository. See [selkies-package-discrepancy](selkies-package-discrepancy.md) reference for the full breakdown. +...is built from the `selkies-project/selkies` git source at install time (along with pixelflux and pcmflux). See [selkies-package-discrepancy](selkies-package-discrepancy.md) reference for the full breakdown. -### How to obtain the correct wheel +### How to obtain the correct package -1. Go to -2. Find the latest successful `selkies-wheel` workflow run -3. Download the `selkies-wheel` artifact (a zip) -4. Extract `selkies-0.0.0.dev0-py3-none-any.whl` -5. Place it in `wheels/` (vendored) or let `selkies-native.sh install` auto-download +The skill installs pixelflux, pcmflux, and selkies from git source during `install`. PyPI `selkies==1.6.1` is the wrong legacy package. The `selkies-wheel` GitHub Actions artifact requires auth and is unreliable. Building from git is the only reliable unattended path. ### Web Client Build (New in Skill) @@ -84,7 +73,7 @@ This eliminates the ~80MB vendored wheels from git — the skill is now self-con ## System Dependencies -Beyond the standard `xvfb xfce4 xfce4-goodies dbus-x11 nginx python3-venv python3-pip`, pixelflux requires VA-API libraries for H.264 encoding: +Beyond the standard `xvfb xfce4 xfce4-goodies dbus-x11 python3-venv python3-pip`, pixelflux requires VA-API libraries for H.264 encoding plus build dependencies for compiling Rust extensions from git: ```bash sudo apt-get install -y libva2 libva-drm2 libva-x11-2 @@ -105,18 +94,7 @@ env DISPLAY=:20 dbus-launch --exit-with-session xfce4-session ## WebSocket Endpoint -Selkies serves the media/input WebSocket at **`/api/websockets`** (NOT `/websockets/primary`, which 404s). nginx must proxy the upgrade to this path: - -```nginx -location / { - proxy_pass http://127.0.0.1:8082; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_read_timeout 86400; -} -``` +Selkies serves the media/input WebSocket at **`/api/websockets`** (NOT `/websockets/primary`, which 404s). Since selkies binds directly to port 3000, the browser connects straight to selkies — no proxy needed. ## Auto-Resize @@ -146,7 +124,7 @@ design, not an oversight: **Hardening for VM/bare-metal hosts:** - Keep the forwarded port **private** (do not expose it publicly), **or** -- Place nginx behind an authenticating proxy (Authelia, OAuth2 Proxy, Cloudflare +- Place selkies behind an authenticating proxy (Authelia, OAuth2 Proxy, Cloudflare Access), **or** - Enable selkies basic-auth — but note it is a single shared credential and weak on its own; treat it as defense-in-depth, not primary access control. From 50b91564b2428bbff21697067a99175312bf2d68 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 20:31:57 +0000 Subject: [PATCH 05/14] fix(webtop): address Greptile findings on cleanup and nginx scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nginx cleanup now checks if nginx is actually listening on our port (3000) before stopping it — unrelated nginx instances on VM/bare-metal hosts are left alone. - Also removes stale /etc/nginx/sites-enabled/selkies config so a later nginx restart won't reload the obsolete proxy. - Rust cleanup now only removes ~/.rustup and ~/.cargo if the install script itself installed them (rust_installed_by_us=1). If the user had a pre-existing Rust toolchain, only the cargo build cache is cleaned (~575MB), preserving the toolchain. --- .../scripts/selkies-native.sh | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 96abbd7..f57701d 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -140,8 +140,10 @@ cmd_install() { 2>&1 | tail -5 # Rust toolchain (required for pixelflux/pcmflux PyO3 builds) + local rust_installed_by_us=0 if ! command -v cargo &>/dev/null; then echo "[rust] installing Rust toolchain..." + rust_installed_by_us=1 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" else @@ -177,7 +179,13 @@ cmd_install() { | awk '{s+=$1} END{print s+0}') echo "[cleanup] removing Rust toolchain (build-time only, ~1.5GB)..." - rm -rf "$HOME/.rustup" "$HOME/.cargo" + if [[ "$rust_installed_by_us" -eq 1 ]]; then + rm -rf "$HOME/.rustup" "$HOME/.cargo" + else + echo "[cleanup] Rust was pre-existing, skipping removal" + # Only clean the cargo build cache, not the toolchain + rm -rf "$HOME/.cargo/registry/cache" "$HOME/.cargo/registry/src" "$HOME/.cargo/git/db" + fi echo "[cleanup] removing selkies source clone (web dist already copied)..." rm -rf "$HOME/.selkies/selkies-src" @@ -246,12 +254,16 @@ cmd_build_web() { cmd_start() { echo "=== selkies-native: start ===" - # 0. Kill legacy nginx if still running (from old skill installs). + # 0. Kill legacy nginx if still listening on our port (from old skill installs). # nginx used to proxy port 3000 → selkies; now selkies binds directly. - if pgrep -x nginx >/dev/null 2>&1; then - echo "[nginx] stopping legacy nginx process..." + # Only target nginx on port 3000 — unrelated nginx instances are left alone. + if ss -tlnp 2>/dev/null | grep -q ":3000.*nginx" || \ + ss -tlnp 2>/dev/null | grep -q ":$SELKIES_PORT.*nginx"; then + echo "[nginx] stopping legacy nginx on port $SELKIES_PORT..." sudo nginx -s quit 2>/dev/null || sudo pkill -x nginx 2>/dev/null || true sleep 0.5 + # Remove stale selkies site config so a later nginx restart won't reload it + [[ -f /etc/nginx/sites-enabled/selkies ]] && sudo rm -f /etc/nginx/sites-enabled/selkies fi # 1. Xvfb From 7430febfe8801e23fdfaa45c70d4e299a757a28c Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 20:56:39 +0000 Subject: [PATCH 06/14] fix(webtop): pin git deps to specific commits for reproducibility Greptile P1: mutable git HEADs can change between installs, causing inconsistent builds. Pin to the current HEAD SHAs for each repo: - pixelflux: bf07c68 - pcmflux: d2683ef - selkies: 1d9b67b Update by running `git ls-remote` on each repo and updating the pins. SKILL.md now documents this process. --- .devcontainer/skills/codespace-webtop/SKILL.md | 11 ++++++----- .../skills/codespace-webtop/scripts/selkies-native.sh | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index e62eb5c..ccb7725 100644 --- a/.devcontainer/skills/codespace-webtop/SKILL.md +++ b/.devcontainer/skills/codespace-webtop/SKILL.md @@ -125,12 +125,13 @@ Validates: OS, sudo, python3, pip3, and all apt packages listed above. ### 2. Install (`install`) 1. Creates venv at `~/.selkies/venv` (or `$SELKIES_VENV_DIR`) -2. Installs pixelflux, pcmflux, and selkies from git source. **Why git source?** selkies `main` branch requires `pixelflux~=2.1.0` and `pcmflux~=2.1.0`, which are unreleased — PyPI only has up to 2.0.0. The 2.1.0 versions exist only in git HEAD. Building from git is the only way to satisfy these deps: +2. Installs pixelflux, pcmflux, and selkies from pinned git commits. **Why git source?** selkies `main` branch requires `pixelflux~=2.1.0` and `pcmflux~=2.1.0`, which are unreleased — PyPI only has up to 2.0.0. The 2.1.0 versions exist only in git HEAD. Pins to specific commits for reproducibility. When updating, get the new HEAD SHAs from `git ls-remote` and update the pins in `selkies-native.sh`: ```bash - source ~/.selkies/venv/bin/activate && source ~/.cargo/env - pip install "git+https://github.com/selkies-project/pixelflux.git" - pip install "git+https://github.com/selkies-project/pcmflux.git" - pip install "git+https://github.com/selkies-project/selkies.git" + source ~/.selkies/venv/bin/activate + pip install \ + "git+https://github.com/selkies-project/pixelflux.git@" \ + "git+https://github.com/selkies-project/pcmflux.git@" \ + "git+https://github.com/selkies-project/selkies.git@" ``` (PyPI `selkies==1.6.1` is the WRONG legacy GStreamer package.) 3. **Build the web client** (`cmd_build_web`): clone the full selkies repo (both `addons/selkies-web-core` and `addons/selkies-dashboard` must be siblings), `npm install` + `npm run build` **selkies-web-core first** (the dashboard's prebuild imports its `dist/selkies-core.js`), then build **selkies-dashboard**, and copy `addons/selkies-dashboard/dist/` → `~/.selkies/web_root`. The web client is **NOT** bundled in the wheel — serve the dashboard, NOT bare web-core (see Pitfalls: bare core = no sidebar). diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index f57701d..b5101df 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -162,9 +162,9 @@ cmd_install() { # only in git HEAD. Build all three from git in order. echo "[pip] installing pixelflux, pcmflux, and selkies from git..." "$VENV_DIR/bin/pip" install --no-cache-dir \ - "git+https://github.com/selkies-project/pixelflux.git" \ - "git+https://github.com/selkies-project/pcmflux.git" \ - "git+https://github.com/selkies-project/selkies.git" + "git+https://github.com/selkies-project/pixelflux.git@bf07c68" \ + "git+https://github.com/selkies-project/pcmflux.git@d2683ef" \ + "git+https://github.com/selkies-project/selkies.git@1d9b67b" # Build and install selkies web frontend (selkies-dashboard + embedded core) echo "[web] building selkies-dashboard web client..." From d9fc385cda3780800ee4a6bff1194f237d27f05c Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 21:29:33 +0000 Subject: [PATCH 07/14] fix(webtop): address remaining Greptile findings 1. Rust ownership detection (P1): check for ~/.cargo and ~/.rustup dirs on disk, not just PATH. If an existing toolchain is off-PATH, we detect it and reuse rather than reinstall and later delete it. 2. Rust installer verification (P2): download rustup-init binary + sha256 checksum separately, verify before executing. No more curl | sh. 3. Install failure propagation (P1): pip install failures now abort with return 1 instead of continuing silently. Also verify selkies is importable after install. 4. Legacy nginx detection (P1): use ss -tln (no -p) to check port occupancy without needing root process metadata. If the port is occupied, try nginx -s quit first, then fuser -k as fallback. --- .../scripts/selkies-native.sh | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index b5101df..db5fad6 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -140,14 +140,38 @@ cmd_install() { 2>&1 | tail -5 # Rust toolchain (required for pixelflux/pcmflux PyO3 builds) + # Only set rust_installed_by_us=1 if WE install it. If the user already + # has a rustup toolchain (even if not on PATH), we must NOT delete it + # during cleanup. local rust_installed_by_us=0 - if ! command -v cargo &>/dev/null; then + if ! command -v cargo &>/dev/null && [[ ! -d "$HOME/.cargo" && ! -d "$HOME/.rustup" ]]; then echo "[rust] installing Rust toolchain..." rust_installed_by_us=1 - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + # Download rustup-init binary + verify sha256 instead of curl | sh + local arch="$(uname -m)" + local dl_base="https://static.rust-lang.org/rustup/dist/${arch}-unknown-linux-gnu" + if ! curl -sSf "${dl_base}/rustup-init.sha256" -o /tmp/rustup-init.sha256; then + echo "[rust] ERROR: could not fetch rustup checksum" + return 1 + fi + curl -sSf "${dl_base}/rustup-init" -o /tmp/rustup-init || { + echo "[rust] ERROR: could not download rustup-init" + return 1 + } + ( cd /tmp && grep -q "rustup-init" rustup-init.sha256 \ + && echo "$(awk '{print $1}' rustup-init.sha256) rustup-init" | sha256sum -c - ) || { + echo "[rust] ERROR: rustup-init checksum verification failed" + return 1 + } + chmod +x /tmp/rustup-init + /tmp/rustup-init -y --default-toolchain stable + rm -f /tmp/rustup-init /tmp/rustup-init.sha256 source "$HOME/.cargo/env" - else + elif command -v cargo &>/dev/null; then echo "[rust] already installed ($(rustc --version))" + else + echo "[rust] existing rustup found on disk (not on PATH), reusing" + source "$HOME/.cargo/env" 2>/dev/null || true fi # Virtualenv @@ -164,7 +188,16 @@ cmd_install() { "$VENV_DIR/bin/pip" install --no-cache-dir \ "git+https://github.com/selkies-project/pixelflux.git@bf07c68" \ "git+https://github.com/selkies-project/pcmflux.git@d2683ef" \ - "git+https://github.com/selkies-project/selkies.git@1d9b67b" + "git+https://github.com/selkies-project/selkies.git@1d9b67b" || { + echo "[pip] ERROR: failed to install pixelflux/pcmflux/selkies from git" + return 1 + } + + # Verify selkies is importable + if ! "$VENV_DIR/bin/python" -c "import selkies" 2>/dev/null; then + echo "[pip] ERROR: selkies installed but not importable" + return 1 + fi # Build and install selkies web frontend (selkies-dashboard + embedded core) echo "[web] building selkies-dashboard web client..." @@ -254,14 +287,20 @@ cmd_build_web() { cmd_start() { echo "=== selkies-native: start ===" - # 0. Kill legacy nginx if still listening on our port (from old skill installs). + # 0. Kill legacy nginx if port $SELKIES_PORT is occupied (from old skill installs). # nginx used to proxy port 3000 → selkies; now selkies binds directly. - # Only target nginx on port 3000 — unrelated nginx instances are left alone. - if ss -tlnp 2>/dev/null | grep -q ":3000.*nginx" || \ - ss -tlnp 2>/dev/null | grep -q ":$SELKIES_PORT.*nginx"; then - echo "[nginx] stopping legacy nginx on port $SELKIES_PORT..." - sudo nginx -s quit 2>/dev/null || sudo pkill -x nginx 2>/dev/null || true + # Don't rely on ss -tlnp process names — unprivileged ss omits them for + # root-owned processes. Instead, check if anything occupies our port and + # attempt a graceful nginx stop. If nginx isn't running, the stop is a no-op. + if ss -tln " sport = :$SELKIES_PORT " 2>/dev/null | grep -q ":$SELKIES_PORT"; then + echo "[nginx] port $SELKIES_PORT occupied — stopping legacy nginx if present..." + sudo nginx -s quit 2>/dev/null || true sleep 0.5 + # If port is still occupied after nginx stop, force-kill any nginx listening on it + if ss -tln " sport = :$SELKIES_PORT " 2>/dev/null | grep -q ":$SELKIES_PORT"; then + sudo fuser -k "$SELKIES_PORT/tcp" 2>/dev/null || true + sleep 0.5 + fi # Remove stale selkies site config so a later nginx restart won't reload it [[ -f /etc/nginx/sites-enabled/selkies ]] && sudo rm -f /etc/nginx/sites-enabled/selkies fi From f71ab469f0cff926be6495ec3ab9ce7e479ef8df Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 21:37:07 +0000 Subject: [PATCH 08/14] docs(webtop): update architecture ref, add disk-optimization reference - architecture.md: reflect new nginx-free stack (selkies on 0.0.0.0:3000), add Migration section for legacy nginx cleanup - Add references/disk-optimization.md: documents ~2.3GB cleanup of build-time artifacts (Rust toolchain, cargo registry, git clone, pip cache) and git commit pinning strategy for reproducibility - SKILL.md: reference new disk-optimization.md --- .../skills/codespace-webtop/SKILL.md | 1 + .../references/architecture.md | 20 ++++--- .../references/disk-optimization.md | 55 +++++++++++++++++++ 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 .devcontainer/skills/codespace-webtop/references/disk-optimization.md diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index ccb7725..98697ab 100644 --- a/.devcontainer/skills/codespace-webtop/SKILL.md +++ b/.devcontainer/skills/codespace-webtop/SKILL.md @@ -246,5 +246,6 @@ codespace-webtop/ - Architecture details: `references/architecture.md` - Troubleshooting: `references/troubleshooting.md` +- Disk optimization: `references/disk-optimization.md` - PyPI selkies==1.6.1 is WRONG: (legacy GStreamer package) - Correct pixelflux-based selkies: (install from git source) diff --git a/.devcontainer/skills/codespace-webtop/references/architecture.md b/.devcontainer/skills/codespace-webtop/references/architecture.md index 7caeea4..2b40903 100644 --- a/.devcontainer/skills/codespace-webtop/references/architecture.md +++ b/.devcontainer/skills/codespace-webtop/references/architecture.md @@ -3,7 +3,7 @@ ## Stack Overview ``` -Browser (port 3000, GitHub auth) → nginx → selkies (127.0.0.1:8082, mode=websockets) +Browser (port 3000, GitHub auth) → selkies (0.0.0.0:3000, mode=websockets) selkies drives pixelflux capture on Xvfb :20 running XFCE pixelflux: Rust X11 capture → H.264/JPEG stripes → WebSocket ``` @@ -14,9 +14,8 @@ Browser (port 3000, GitHub auth) → nginx → selkies (127.0.0.1:8082, mode=web |-----------|---------|------|---------| | Xvfb | `Xvfb :20` | — | Headless X11 display (1920x1080x24) | | XFCE | `xfce4-session` | — | Desktop environment (window manager, panel, file manager) | -| selkies | `selkies` | 127.0.0.1:8082 | Serves React client + WebSocket media/input protocol | +| selkies | `selkies` | 0.0.0.0:3000 | Serves React client + WebSocket media/input protocol | | pixelflux | (Rust .so) | — | X11 screen capture → H.264/JPEG stripes | -| nginx | `nginx` | 0.0.0.0:3000 | Reverse proxy with WS upgrade | ## Data Flow @@ -40,10 +39,8 @@ Browser (port 3000, GitHub auth) → nginx → selkies (127.0.0.1:8082, mode=web ``` ~/.selkies/ ├── venv/ # Python venv with selkies + pixelflux + pcmflux -├── wheels/ # Vendored selkies wheel (for offline install) +├── web_root/ # Built React dashboard (copied at install) └── pid/ # PID files (xvfb, xfce, selkies) - -/etc/nginx/sites-enabled/selkies # nginx reverse proxy config ``` ## Logs @@ -51,4 +48,13 @@ Browser (port 3000, GitHub auth) → nginx → selkies (127.0.0.1:8082, mode=web - Xvfb: `/tmp/selkies-logs/xvfb.log` - XFCE: `/tmp/selkies-logs/xfce.log` - selkies: `/tmp/selkies-logs/selkies.log` -- nginx: `/var/log/nginx/error.log` + +## Migration: Legacy nginx Cleanup + +The original architecture used nginx as a reverse proxy (`3000 → 127.0.0.1:8082`). The current architecture binds selkies directly to `0.0.0.0:3000`. On upgrades, the `start` command handles legacy artifacts: + +1. **Port check** — If port 3000 (or `$SELKIES_PORT`) is occupied, attempt graceful nginx stop via `nginx -s quit`. +2. **Force cleanup** — If port remains occupied, use `fuser -k` to kill the occupying process. +3. **Config removal** — Delete `/etc/nginx/sites-enabled/selkies` so a later nginx restart won't reload the stale proxy config. + +This ensures clean migration from old installations without affecting unrelated nginx instances on the same host. diff --git a/.devcontainer/skills/codespace-webtop/references/disk-optimization.md b/.devcontainer/skills/codespace-webtop/references/disk-optimization.md new file mode 100644 index 0000000..1ef3288 --- /dev/null +++ b/.devcontainer/skills/codespace-webtop/references/disk-optimization.md @@ -0,0 +1,55 @@ +# Disk Space Optimization + +## Build-Time vs Runtime Artifacts + +The webtop install compiles pixelflux and pcmflux from Rust source (they require PyO3 Rust extensions). After compilation, only the resulting `.so` files in the venv are needed at runtime. The following are **build-time only** and can be safely removed after install: + +| Artifact | Size | Why removable | +|----------|------|---------------| +| `~/.rustup` | ~1.5GB | Rust toolchain (compiler, std lib) — only needed to compile extensions | +| `~/.cargo/registry` | ~575MB | Cargo crate cache — only needed during build | +| `~/.selkies/selkies-src/` | ~170MB | Full git clone + node_modules — only `web_root/` dist matters | +| `~/.cache/pip` | ~32MB | pip download cache | + +**Total savings: ~2.3GB** (from ~3GB -> ~250MB runtime footprint). + +## Cleanup Procedure (in `cmd_install`) + +```bash +# Track whether we installed Rust ourselves +local rust_installed_by_us=0 +if ! command -v cargo &>/dev/null && [[ ! -d "$HOME/.cargo" && ! -d "$HOME/.rustup" ]]; then + rust_installed_by_us=1 + # ... install rustup ... +fi + +# After pip install + web build: +if [[ "$rust_installed_by_us" -eq 1 ]]; then + # We installed it -- safe to remove entirely + rm -rf "$HOME/.rustup" "$HOME/.cargo" +else + # User had pre-existing toolchain -- only clean build cache + rm -rf "$HOME/.cargo/registry/cache" "$HOME/.cargo/registry/src" "$HOME/.cargo/git/db" +fi +rm -rf "$HOME/.selkies/selkies-src" "$HOME/.cache/pip" +``` + +## Reinstalling After Cleanup + +If the user runs `install` again: +- Rust toolchain will be re-downloaded if `rust_installed_by_us=1` (we removed it) +- Cargo registry will be repopulated on first build +- `selkies-src` will be re-cloned +- This is correct behavior -- cleanup trades disk for re-download time on reinstall + +## Git Commit Pinning (Reproducibility) + +The three git dependencies are pinned to specific commits: + +| Repo | Pin | How to update | +|------|-----|---------------| +| pixelflux | `bf07c68` | `git ls-remote https://github.com/selkies-project/pixelflux.git HEAD` | +| pcmflux | `d2683ef` | `git ls-remote https://github.com/selkies-project/pcmflux.git HEAD` | +| selkies | `1d9b67b` | `git ls-remote https://github.com/selkies-project/selkies.git HEAD` | + +Update pins in `selkies-native.sh` when new features/fixes are needed. Never use mutable `@HEAD` or branch names -- they break reproducibility. From c8bc9ff17adefd2f390ed8122d5678087f50b104 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 21:47:05 +0000 Subject: [PATCH 09/14] fix(webtop): address two more Greptile findings 1. Web build failure propagation (P1): cmd_build_web now returns non-zero on git clone failure, npm build failure, or dashboard dist copy failure. cmd_install propagates these and aborts instead of writing .installed with an empty web_root. 2. Cleanup kills unrelated port owners (P1): before force-killing a process on the target port, verify with sudo ss -tlnp that the listener is actually nginx. If it's an unrelated service, warn and abort instead of killing it. --- .../scripts/selkies-native.sh | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index db5fad6..776a961 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -258,7 +258,10 @@ cmd_build_web() { if [[ ! -d "$repo_dir/.git" ]]; then echo "[web] cloning selkies (full repo, both addons needed)..." rm -rf "$repo_dir" - git clone --depth 1 https://github.com/selkies-project/selkies.git "$repo_dir" 2>&1 | tail -3 + if ! git clone --depth 1 https://github.com/selkies-project/selkies.git "$repo_dir" 2>&1 | tail -3; then + echo "[web] ERROR: git clone failed" + return 1 + fi else echo "[web] updating existing clone..." (cd "$repo_dir" && git pull --depth 1) 2>/dev/null || true @@ -267,17 +270,27 @@ cmd_build_web() { build_addon() { local dir="$1" echo "[web] npm build in $dir ..." - ( cd "$dir" && { npm ci --no-audit --no-fund 2>/dev/null || npm install --no-audit --no-fund; } && npm run build ) 2>&1 | tail -8 + if ! ( cd "$dir" && { npm ci --no-audit --no-fund 2>/dev/null || npm install --no-audit --no-fund; } && npm run build ) 2>&1 | tail -8; then + echo "[web] ERROR: npm build failed in $dir" + return 1 + fi } # web-core first (dashboard prebuild imports its dist), then dashboard - build_addon "$web_core_dir" - build_addon "$dashboard_dir" + if ! build_addon "$web_core_dir"; then + return 1 + fi + if ! build_addon "$dashboard_dir"; then + return 1 + fi # Serve the DASHBOARD dist (has the sidebar + embeds the Core) mkdir -p "$web_dist" rm -rf "$web_dist"/* 2>/dev/null || true - cp -r "$dashboard_dir/dist/"* "$web_dist/" + if ! cp -r "$dashboard_dir/dist/"* "$web_dist/"; then + echo "[web] ERROR: failed to copy dashboard dist" + return 1 + fi echo "[web] built and copied dashboard to $web_dist" ls -la "$web_dist/" @@ -292,14 +305,23 @@ cmd_start() { # Don't rely on ss -tlnp process names — unprivileged ss omits them for # root-owned processes. Instead, check if anything occupies our port and # attempt a graceful nginx stop. If nginx isn't running, the stop is a no-op. + # Only force-kill if the process on the port IS nginx (check via ss -tlnp + # with sudo to see process name). Unrelated services on the port are left alone. if ss -tln " sport = :$SELKIES_PORT " 2>/dev/null | grep -q ":$SELKIES_PORT"; then echo "[nginx] port $SELKIES_PORT occupied — stopping legacy nginx if present..." sudo nginx -s quit 2>/dev/null || true sleep 0.5 - # If port is still occupied after nginx stop, force-kill any nginx listening on it + # If port is still occupied after nginx stop, check if the remaining + # process is nginx before force-killing if ss -tln " sport = :$SELKIES_PORT " 2>/dev/null | grep -q ":$SELKIES_PORT"; then - sudo fuser -k "$SELKIES_PORT/tcp" 2>/dev/null || true - sleep 0.5 + if sudo ss -tlnp " sport = :$SELKIES_PORT " 2>/dev/null | grep -q "nginx"; then + echo "[nginx] forcing kill of nginx on port $SELKIES_PORT..." + sudo fuser -k "$SELKIES_PORT/tcp" 2>/dev/null || true + sleep 0.5 + else + echo "[nginx] WARNING: port $SELKIES_PORT occupied by non-nginx process, not killing" + return 1 + fi fi # Remove stale selkies site config so a later nginx restart won't reload it [[ -f /etc/nginx/sites-enabled/selkies ]] && sudo rm -f /etc/nginx/sites-enabled/selkies From b8cedb18c94b010e5ea84b66a31f8a33b727ed23 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 22:01:45 +0000 Subject: [PATCH 10/14] fix(webtop): pin web client commit to match Python packages Greptile P1: web client was cloned from unpinned HEAD while Python deps (pixelflux, pcmflux, selkies) are pinned to specific commits. Now the web clone also checks out the pinned selkies commit (1d9b67be6f9c695f187a0509a3c1d3b3e204807b) for full reproducibility. --- .../codespace-webtop/scripts/selkies-native.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 776a961..ab48ece 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -254,7 +254,9 @@ cmd_build_web() { local dashboard_dir="$repo_dir/addons/selkies-dashboard" local web_dist="$HOME/.selkies/web_root" - # Clone full selkies repo once (both addons must be siblings) + # Clone full selkies repo once (both addons must be siblings). + # Pin to the same commit as the Python package for reproducibility. + local SELKIES_WEB_COMMIT="1d9b67be6f9c695f187a0509a3c1d3b3e204807b" if [[ ! -d "$repo_dir/.git" ]]; then echo "[web] cloning selkies (full repo, both addons needed)..." rm -rf "$repo_dir" @@ -262,9 +264,17 @@ cmd_build_web() { echo "[web] ERROR: git clone failed" return 1 fi + # Checkout the pinned commit + (cd "$repo_dir" && git checkout -q "$SELKIES_WEB_COMMIT") || { + echo "[web] ERROR: failed to checkout commit $SELKIES_WEB_COMMIT" + return 1 + } else - echo "[web] updating existing clone..." - (cd "$repo_dir" && git pull --depth 1) 2>/dev/null || true + echo "[web] updating existing clone to pinned commit..." + (cd "$repo_dir" && git fetch --depth 1 origin "$SELKIES_WEB_COMMIT" && git checkout -q "$SELKIES_WEB_COMMIT") 2>/dev/null || { + echo "[web] ERROR: failed to update to pinned commit $SELKIES_WEB_COMMIT" + return 1 + } fi build_addon() { From 38c48b8866c9f80c66f0818ad3acc452fc4da727 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 22:04:36 +0000 Subject: [PATCH 11/14] fix(webtop): consolidate SHA pins into env vars Centralize all commit pins as env vars at top of selkies-native.sh: - SELKIES_WEB_COMMIT (selkies web client) - SELKIES_PIXELFLUX_COMMIT (pixelflux) - SELKIES_PCMFLUX_COMMIT (pcmflux) Consumers (pip install, SKILL.md) reference these env vars instead of hardcoded SHAs. Makes it easy to update pins: just set the env vars or edit the defaults. --- .devcontainer/skills/codespace-webtop/SKILL.md | 4 ++++ .../codespace-webtop/scripts/selkies-native.sh | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index 98697ab..dcbd44d 100644 --- a/.devcontainer/skills/codespace-webtop/SKILL.md +++ b/.devcontainer/skills/codespace-webtop/SKILL.md @@ -171,6 +171,10 @@ Environment variables (all optional, with defaults): | `XVFB_DISPLAY` | `:20` | Xvfb display number | | `XVFB_SCREEN` | `1920x1080x24` | Screen resolution | | `SELKIES_FRAMERATE` | `30` | Capture framerate | +| `SELKIES_WEB_ROOT` | `~/.selkies/web_root` | Web client root dir | +| `SELKIES_WEB_COMMIT` | `1d9b67be6f9c695f187a0509a3c1d3b3e204807b` | Pinned selkies web commit | +| `SELKIES_PIXELFLUX_COMMIT` | `bf07c68` | Pinned pixelflux commit | +| `SELKIES_PCMFLUX_COMMIT` | `d2683ef` | Pinned pcmflux commit | ## Pitfalls diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index ab48ece..c448939 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -18,6 +18,12 @@ SELKIES_PORT="${SELKIES_PORT:-3000}" SELKIES_FRAMERATE="${SELKIES_FRAMERATE:-30}" # Web client root (built by cmd_build_web during install) WEB_ROOT="${SELKIES_WEB_ROOT:-$HOME/.selkies/web_root}" +# Pinned selkies commit for web reproducibility +SELKIES_WEB_COMMIT="${SELKIES_WEB_COMMIT:-1d9b67be6f9c695f187a0509a3c1d3b3e204807b}" +# Pinned Pixelflux commit (required by selkies main; unpinnable on PyPI) +SELKIES_PIXELFLUX_COMMIT="${SELKIES_PIXELFLUX_COMMIT:-bf07c68}" +# Pinned PCMFlux commit (required by selkies main; unpinnable on PyPI) +SELKIES_PCMFLUX_COMMIT="${SELKIES_PCMFLUX_COMMIT:-d2683ef}" # User home for session config (auto-detect) USER_HOME="${SUDO_USER_HOME:-$HOME}" @@ -183,12 +189,13 @@ cmd_install() { # PyPI selkies==1.6.1 is the legacy GStreamer package (wrong). # selkies main branch requires pixelflux~=2.1.0 and pcmflux~=2.1.0, # which are unreleased on PyPI (max: 2.0.0). The 2.1.0 versions exist - # only in git HEAD. Build all three from git in order. - echo "[pip] installing pixelflux, pcmflux, and selkies from git..." + # only in git HEAD. Pins to specific commits for reproducibility — update + # these SHAs when upstream changes, via env vars or direct edit. + echo "[pip] installing pixelflux, pcmflux, and selkies from pinned git..." "$VENV_DIR/bin/pip" install --no-cache-dir \ - "git+https://github.com/selkies-project/pixelflux.git@bf07c68" \ - "git+https://github.com/selkies-project/pcmflux.git@d2683ef" \ - "git+https://github.com/selkies-project/selkies.git@1d9b67b" || { + "git+https://github.com/selkies-project/pixelflux.git@${SELKIES_PIXELFLUX_COMMIT}" \ + "git+https://github.com/selkies-project/pcmflux.git@${SELKIES_PCMFLUX_COMMIT}" \ + "git+https://github.com/selkies-project/selkies.git@${SELKIES_WEB_COMMIT}" || { echo "[pip] ERROR: failed to install pixelflux/pcmflux/selkies from git" return 1 } From aff52cd15dd3a76c47198d507b8d7738fdd36e7a Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 22:40:42 +0000 Subject: [PATCH 12/14] fix(webtop): address two new Greptile P1 findings 1. Web commit override ignored: cmd_build_web now uses global SELKIES_WEB_COMMIT env var instead of hardcoded local value. Override via env var now works end-to-end. 2. Web build failures report success: cmd_install now checks cmd_build_web return value and aborts on failure. --- .../skills/codespace-webtop/scripts/selkies-native.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index c448939..328754b 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -208,7 +208,7 @@ cmd_install() { # Build and install selkies web frontend (selkies-dashboard + embedded core) echo "[web] building selkies-dashboard web client..." - cmd_build_web + cmd_build_web || return 1 # ── Cleanup: remove build-time-only artifacts to save disk ────────── # Rust toolchain + cargo registry are only needed during pip install @@ -263,7 +263,7 @@ cmd_build_web() { # Clone full selkies repo once (both addons must be siblings). # Pin to the same commit as the Python package for reproducibility. - local SELKIES_WEB_COMMIT="1d9b67be6f9c695f187a0509a3c1d3b3e204807b" + # Uses the global SELKIES_WEB_COMMIT env var (can be overridden externally). if [[ ! -d "$repo_dir/.git" ]]; then echo "[web] cloning selkies (full repo, both addons needed)..." rm -rf "$repo_dir" From cf68967c2464b8604dd15674471c62f7d3109377 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 22:50:47 +0000 Subject: [PATCH 13/14] docs(webtop): add threat model comment for rustup checksum trust boundary Documents the supply-chain risk model: checksum + TLS detects accidental corruption but not a compromised distribution origin. Applies equally to npm/pip/cargo/apt. Per Karpathy guidelines: be explicit about assumptions, no silent assumptions in implementation. --- .../skills/codespace-webtop/scripts/selkies-native.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index 328754b..ec2c491 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -164,6 +164,15 @@ cmd_install() { echo "[rust] ERROR: could not download rustup-init" return 1 } + # Verify checksum before executing. Note: this detects accidental + # corruption or a mirror with corrupted data, but does not address a + # compromised distribution origin — if static.rust-lang.org itself is + # serving a malicious binary with a matching forged checksum, the + # executable still runs with the installer's privileges. The checksum + # comparison assumes the download channel (HTTPS) provides transport + # integrity against accidental corruption; supply-chain risk is + # out-of-scope for this installer and applies equally to all package + # managers (npm, pip, cargo, apt). ( cd /tmp && grep -q "rustup-init" rustup-init.sha256 \ && echo "$(awk '{print $1}' rustup-init.sha256) rustup-init" | sha256sum -c - ) || { echo "[rust] ERROR: rustup-init checksum verification failed" From a54e84a53fb89bd43b32e9f40efa0987fa6a3089 Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 7 Sep 2026 22:59:57 +0000 Subject: [PATCH 14/14] fix(webtop): use SELKIES_WEB_ROOT in cmd_build_web Respects the env var override so web client builds to the correct destination matching cmd_start's serve path. --- .devcontainer/skills/codespace-webtop/scripts/selkies-native.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh index ec2c491..4e1d4e0 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -268,7 +268,7 @@ cmd_build_web() { local repo_dir="$HOME/.selkies/selkies-src" local web_core_dir="$repo_dir/addons/selkies-web-core" local dashboard_dir="$repo_dir/addons/selkies-dashboard" - local web_dist="$HOME/.selkies/web_root" + local web_dist="$WEB_ROOT" # Clone full selkies repo once (both addons must be siblings). # Pin to the same commit as the Python package for reproducibility.