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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ METAMCP_POSTGRES_PORT=5432
# Public URL MetaMCP reports itself as; update if reverse-proxied.
METAMCP_APP_URL=http://localhost:12008

# ==============================================================================
# KALI-DESKTOP (optional — Selkies web desktop, AMD GPU accelerated)
# ==============================================================================
# Host ports for the Selkies web desktop (loopback-bound by default).
KALI_DESKTOP_HTTP_PORT=3010
KALI_DESKTOP_HTTPS_PORT=3011

# Host home directory bind-mounted read-write into the desktop at /home/jond.
KALI_DESKTOP_HOME_DIR=/home/jond

# AMD Radeon 780M (or other iGPU) render node. Check with: ls /dev/dri
KALI_GPU_RENDER_NODE=/dev/dri/renderD128

# Host group IDs owning /dev/dri/render* and /dev/dri/card*.
# Check with: getent group render video
KALI_GPU_RENDER_GID=990
KALI_GPU_VIDEO_GID=44

# ==============================================================================
# LOCAL STORAGE VOLUMES
# ==============================================================================
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
**/config/
**/data/
valkey-data/
kali-desktop/opt/

# OS and system files
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Use the current configurations as guides for implementing new ones:
* **qBittorrent**: Capped at `2048M` memory. Binds media & download environment paths.
* **Jellyfin**: Designed with `network_mode: host` to access local network streams and binds host devices `/dev/dri` and `/dev/kfd` for native AMD hardware-accelerated transcoding.
* **ChangeDetection**: Paired with `browser-sockpuppet-chrome` (running headless Chromium with `SYS_ADMIN` capability, `init: true`, and capped at `1536M` memory/`2.0` CPU limits).
* **Kali Desktop**: LinuxServer Selkies-based Kali (`linuxserver/docker-baseimage-selkies`). AMD/Vulkan GPU accel via `/dev/dri` (`DRINODE`/`DRI_NODE`, no CUDA — NVIDIA-only), CPU/llvmpipe fallback documented inline. Bind-mounts the real host `/home/jond`. **Deliberately omits `no-new-privileges:true`** (breaks `sudo`, which Kali's toolkit — nmap, aircrack-ng, apt — depends on) and adds `NET_RAW`/`NET_ADMIN` on top of the standard s6-overlay cap set; see `docs/HARD-WON-GOTCHAS.md`. Loopback-bound ports to offset the reduced hardening. Off by default in the root `include:`.

---

Expand Down
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This repository contains a highly optimized, production-grade, and resilient mul
6. **[Flaresolverr](https://github.com/FlareSolverr/FlareSolverr)**: (Optional) Proxy server to bypass DDoS protection mechanisms for scraping and indices.
7. **[Browser](https://github.com/coollabsio/openclaw)**: (Optional) Shared Chrome/CDP sidecar — a real browser on tap for scraping and automation, with a web desktop UI. Migrated here 2026-09-07 from its own repo.
8. **[MetaMCP](https://github.com/metatool-ai/metamcp)**: (Optional) MCP gateway with a bundled hardened Postgres 18. Migrated here 2026-09-07 from its own repo.
9. **[Kali Desktop](https://docs.linuxserver.io/images/docker-kali-linux/)**: (Optional) Full Kali Linux desktop streamed to the browser via Selkies, with AMD/Vulkan GPU acceleration for a Radeon 780M iGPU and the host's `/home/jond` mounted in.

> **Production note:** production self-hosting runs on [Cloudron](https://cloudron.io) (automatic maintenance, updates and backups). This repo is the **local Docker + Docker Compose** side. The former Coolify fleet is retired; the hard-won operational lessons from it are preserved in [`docs/HARD-WON-GOTCHAS.md`](docs/HARD-WON-GOTCHAS.md).

Expand Down Expand Up @@ -108,6 +109,65 @@ docker compose ps
| **Browser** (CDP) | `9223` | `9223` | `BROWSER_CDP_PORT` |
| **MetaMCP** | `12008` | `12008` | `METAMCP_PORT` |
| **Daily Stars Explorer** | `8080` | `8080` | `DAILY_STARS_PORT` |
| **Kali Desktop** (HTTP) | `3010` | `3000` | `KALI_DESKTOP_HTTP_PORT` |
| **Kali Desktop** (HTTPS) | `3011` | `3001` | `KALI_DESKTOP_HTTPS_PORT` |

---

## 🖥️ Kali Desktop: GPU, persistence, and installing apps

Optional service, off by default. Uncomment `./kali-desktop/docker-compose.yml`
in the root `include:` block to enable it.

**Start it:**
```bash
docker compose up -d kali-desktop
```

**Access it:** open `https://localhost:3011` (accept the self-signed cert —
HTTPS is required for clipboard/audio/file transfer). The plain HTTP port
(`3010`) also works for a quick check but skips those features. Both are
loopback-bound by default; widen only behind a VPN or reverse proxy, since
this container has `sudo` and full access to Kali's toolkit.

**GPU (AMD Radeon 780M):** accelerated by default via `/dev/dri` + Mesa/Vulkan
(no CUDA — that's NVIDIA-only). If `KALI_GPU_RENDER_GID`/`KALI_GPU_VIDEO_GID`
in `.env` don't match your host, check with `getent group render video`. To
fall back to CPU/software rendering (Mesa llvmpipe), comment out the
`devices:`, `group_add:`, and `DRINODE`/`DRI_NODE`/`PIXELFLUX_WAYLAND` lines in
`kali-desktop/docker-compose.yml` — no other changes needed.

**What persists across `docker compose up -d --force-recreate`:**
- `kali-desktop/config/` → the desktop user's home (`/config`): dotfiles,
`~/.config`, `~/.local`, Desktop, Downloads.
- `kali-desktop/opt/` → `/opt`, where many `.deb` packages (including
Obsidian) install their payload.
- Your real `/home/jond`, bind-mounted read-write at the same path.

**Installing a manual `.deb` app (e.g. Obsidian) so it survives a recreate:**

Method A — no root, guaranteed to persist (recommended):
```bash
mkdir -p ~/Applications && cd ~/Applications
wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.13.7/obsidian_1.13.7_amd64.deb
dpkg-deb -x obsidian_1.13.7_amd64.deb obsidian
mkdir -p ~/.local/bin ~/.local/share/applications
ln -sf ~/Applications/obsidian/opt/Obsidian/obsidian ~/.local/bin/obsidian
# copy the app's .desktop file and fix its Exec= line to the symlink above
cp ~/Applications/obsidian/usr/share/applications/*.desktop ~/.local/share/applications/
```
Everything here lives under `/config`, which is already persisted.

Method B — real `apt`/`dpkg` install (integrates with the package manager, but
needs a re-run after recreate):
```bash
sudo dpkg -i obsidian_1.13.7_amd64.deb
```
The payload lands in `/opt` (persisted), but the `dpkg` database entry and the
`/usr/bin` symlink/desktop entry are not (only `/config` and `/opt` are
volumed). After a container recreate, keep the `.deb` under `~/Downloads`
(persisted) and re-run `sudo dpkg -i` — it's fast since the `/opt` payload is
already there, it just relinks.

---

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include:
# - path: ./flaresolverr/docker-compose.yml
# - path: ./browser/docker-compose.yml
# - path: ./metamcp/docker-compose.yml
# - path: ./kali-desktop/docker-compose.yml
- path: ./searxng/docker-compose.yml
- path: ./jellyfin/docker-compose.yml
- path: ./qbittorrent/docker-compose.yml
11 changes: 11 additions & 0 deletions docs/HARD-WON-GOTCHAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ actually needed was the same privilege-drop set as Postgres above, for the same
underlying reason. **Test the actual image before importing advice about its
class.**

**`security_opt: [no-new-privileges:true]` silently breaks `sudo` (and any
other setuid binary).** It stops the kernel from honoring the setuid bit on
`execve`, which is how `sudo` gains root in the first place — the binary still
runs, it just never elevates, so the failure mode is "command ran, did
nothing privileged" rather than an obvious permission error. Fine for images
that never need setuid escalation (the vast majority in this repo). Wrong for
`kali-desktop`, whose entire toolkit (`nmap` raw/SYN scans, `aircrack-ng`,
wireless tooling, `apt` itself) is built around `sudo`. That service
intentionally omits the flag and relies on loopback-only port binding instead
— see `kali-desktop/docker-compose.yml`.

**Passing local tests do not guarantee the config survives a real first boot.** A
second image in the same deployment passed local testing under `cap_drop: ALL`
and then crash-looped in production, because the local runs never combined the
Expand Down
103 changes: 103 additions & 0 deletions kali-desktop/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
services:
kali-desktop:
# Kali Linux built on linuxserver/docker-baseimage-selkies (s6-overlay, WebRTC
# desktop streaming). Pinned to an immutable content-addressed tag, not
# amd64-latest — see AGENTS.md "Pinned Image Versions".
image: lscr.io/linuxserver/kali-linux:amd64-45f3494e-ls127
container_name: kali-desktop
restart: unless-stopped
shm_size: "1gb" # LSIO desktop images recommend >=1gb; XFCE/browsers crash on the 64MB default.
env_file:
- path: ../.env
required: false
- path: ../.env.local
required: false
- path: .env
required: false
- path: .env.local
required: false
environment:
- PUID=${PUID:-1000}
# This service bind-mounts the real /home/jond (below) — set PGID in your
# .env to match `id -g jond` on the host (typically 1000) for correct file
# ownership. The repo-wide PGID default (100) is tuned for shared media
# groups on other services and is usually wrong here.
- PGID=${PGID:-1000}
- TZ=${TZ:-Europe/Paris}
# AMD/Mesa GPU acceleration for the Radeon 780M iGPU (Vulkan/EGL via /dev/dri).
# No NVIDIA/CUDA vars — CUDA is NVIDIA-only and this GPU is AMD.
- DRINODE=${KALI_GPU_RENDER_NODE:-/dev/dri/renderD128}
- DRI_NODE=${KALI_GPU_RENDER_NODE:-/dev/dri/renderD128} # same node as DRINODE = zero-copy encode
- PIXELFLUX_WAYLAND=true # GPU-accelerated Wayland/EGL path; auto-falls back to X11 if the CPU lacks AVX2
# --- GPU passthrough (AMD Radeon 780M) ---
# To run CPU-only (software rendering via Mesa llvmpipe) instead — e.g. no
# GPU present, or debugging a GPU-related crash — comment out `devices:`,
# `group_add:`, and the DRINODE/DRI_NODE/PIXELFLUX_WAYLAND lines above.
# Selkies/Mesa will automatically fall back to llvmpipe with no other
# changes needed; expect much higher CPU usage and lower frame rate.
devices:
- /dev/dri:/dev/dri
group_add:
# Host GIDs owning /dev/dri/render* and /dev/dri/card* — check with
# `getent group render video` on the host and override in .env if different.
- "${KALI_GPU_RENDER_GID:-990}"
- "${KALI_GPU_VIDEO_GID:-44}"
ports:
# Loopback-bound per this repo's convention (admin/desktop interface).
# The container gets full GUI + sudo access to run Kali's toolkit, so
# treat it like a local admin surface: widen only behind a VPN/reverse
# proxy with SELKIES_MASTER_TOKEN set, never expose raw to the Internet
# (upstream's own warning: "privileged access to the host system").
- "127.0.0.1:${KALI_DESKTOP_HTTP_PORT:-3010}:3000" # web desktop, HTTP
- "127.0.0.1:${KALI_DESKTOP_HTTPS_PORT:-3011}:3001" # web desktop, HTTPS (required for clipboard/audio)
volumes:
# abc/PUID user's home inside the container — dotfiles, ~/.config,
# ~/.local, Desktop, Downloads. Everything installed or configured
# through the desktop session that doesn't touch system paths lives
# here and survives `docker compose up -d --force-recreate`.
- ./config:/config
# Payload directory for .deb apps that install into /opt (e.g. Obsidian).
# See README.md for the two supported ways to make manually-installed
# .deb apps survive a container recreate.
- ./opt:/opt
# Real host home directory for jond, mounted read-write so the desktop
# session can browse/edit the same files as the host.
- ${KALI_DESKTOP_HOME_DIR:-/home/jond}:/home/jond
# s6-overlay root->PUID/PGID drop dance — same set as jellyfin/qbittorrent/
# changedetection/postgres in this repo (docs/HARD-WON-GOTCHAS.md). NET_RAW
# and NET_ADMIN are added on top because Kali's core toolkit (nmap SYN/raw
# scans, arp/ARP spoofing tools, wireless tools) needs them to function at all.
cap_drop:
- ALL
cap_add:
- CHOWN
- FOWNER
- DAC_OVERRIDE
- SETUID
- SETGID
- NET_RAW
- NET_ADMIN
# Deliberately NOT setting `security_opt: [no-new-privileges:true]` here,
# unlike every other service in this repo. Kali's toolkit is designed
# around `sudo` (setuid) for tools that need root — nmap, aircrack-ng,
# wireless/BT tooling, apt itself. no-new-privileges blocks the kernel from
# honoring the setuid bit on execve, which silently breaks sudo entirely.
# Mitigate by keeping this on loopback-only (see `ports:` above) instead of
# dropping the sandboxing another way.
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:3000/ -o /dev/null || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s # full XFCE/Selkies desktop boot is slower than a typical service
deploy:
resources:
limits:
cpus: "4.0"
memory: 6144M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Loading