diff --git a/.devcontainer/skills/codespace-webtop/SKILL.md b/.devcontainer/skills/codespace-webtop/SKILL.md index 9788fcc..dcbd44d 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,20 @@ 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 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 + 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). -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 +148,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 +166,15 @@ 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 | +| `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 @@ -142,12 +183,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 +201,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 +240,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 @@ -208,5 +250,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: (GitHub Actions `selkies-wheel` artifact) +- 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. 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..4e1d4e0 100755 --- a/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh +++ b/.devcontainer/skills/codespace-webtop/scripts/selkies-native.sh @@ -1,29 +1,29 @@ #!/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}" +# 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}" @@ -118,6 +118,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,66 +129,123 @@ 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) + # 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 && [[ ! -d "$HOME/.cargo" && ! -d "$HOME/.rustup" ]]; then + echo "[rust] installing Rust toolchain..." + rust_installed_by_us=1 + # 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 + } + # 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" + 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" + 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 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 + # 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. 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@${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 + } - if [[ ! -f "$SELKIES_WHEEL" ]]; then - echo "[wheel] ERROR: selkies wheel not available" - echo " See SKILL.md for manual build from git source" + # 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 - echo "[pip] installing pixelflux, pcmflux, and selkies wheel..." - "$VENV_DIR/bin/pip" install --no-cache-dir \ - pixelflux pcmflux \ - "$SELKIES_WHEEL" - # Build and install selkies web frontend (selkies-dashboard + embedded core) - echo "[web] building selkies-dashboard web client (vite)..." - 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" + echo "[web] building selkies-dashboard web client..." + cmd_build_web || return 1 + + # ── 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)..." + 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" + + 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" @@ -195,39 +254,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 @@ -242,41 +268,91 @@ 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) + # Clone full selkies repo once (both addons must be siblings). + # Pin to the same commit as the Python package for reproducibility. + # 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" - 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 + # 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() { 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/" } -# ── start: Xvfb → XFCE → selkies → nginx ─────────────────────────────── +# ── start: Xvfb → XFCE → selkies ───────────────────────────────────── cmd_start() { echo "=== selkies-native: start ===" + # 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. + # 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, check if the remaining + # process is nginx before force-killing + if ss -tln " sport = :$SELKIES_PORT " 2>/dev/null | grep -q ":$SELKIES_PORT"; then + 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 + fi + # 1. Xvfb echo "[Xvfb] starting on $XVFB_DISPLAY" local xvfb_pid="$(read_pid "$(pid_file xvfb)")" @@ -323,9 +399,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 +417,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 +505,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 +521,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 +594,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 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.