From e082b1fe91cf0a05c2858a9167fa4ac008e38c79 Mon Sep 17 00:00:00 2001 From: Paul Vasiletz Date: Fri, 4 Sep 2026 16:53:18 +0300 Subject: [PATCH 1/4] landlock | allow GPU access through Landlock on WSL and Linux --- profile/policy.py | 30 +++++++++++++++++++++++++++--- profile/policy.yaml | 7 +++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/profile/policy.py b/profile/policy.py index 981718af1..538cd13a0 100644 --- a/profile/policy.py +++ b/profile/policy.py @@ -1,5 +1,6 @@ import os import enum +import glob import yaml import json from py_landlock import Landlock, AccessFs @@ -72,11 +73,13 @@ class FileSystemPolicy: | AccessFs.MAKE_SOCK) READ_WRITE_FILE_ACCESS = (AccessFs.READ_FILE | AccessFs.WRITE_FILE | AccessFs.TRUNCATE) + DEVICE_ACCESS = AccessFs.READ_FILE | AccessFs.WRITE_FILE | AccessFs.IOCTL_DEV def __init__(self): self._compatibility = LandLockCompatibility.BEST_EFFORT self._read_only = [] self._read_write = [] + self._device_access = [] def load_file(self, path: str|Path): logger.info(f"Loading policy from file {path}") @@ -115,22 +118,43 @@ def load_dict(self, policy: dict): rw.append(os.getcwd()) self._read_only = [Path(f'{p}') for p in ro] self._read_write = [Path(f'{p}') for p in rw] + self._device_access = list(fs.get('device_access', []) or []) if fs else [] + + def _resolve_device_paths(self) -> list[Path]: + """Expand device_access glob patterns to existing device files. + + Patterns that match nothing on the current platform (e.g. NVIDIA + device nodes on WSL2, or /dev/dxg on bare Linux) are skipped, so the + same policy file works unmodified on both. + + Returns: + Resolved, deduplicated paths of device files that actually exist. + """ + resolved = set() + for pattern in self._device_access: + resolved.update(Path(match).resolve() for match in glob.glob(pattern)) + return list(resolved) def apply(self): rod = list(filter(lambda p: p.is_dir(), self._read_only)) rof = list(filter(lambda p: not p.is_dir(), self._read_only)) rwd = list(filter(lambda p: p.is_dir(), self._read_write)) rwf = list(filter(lambda p: not p.is_dir(), self._read_write)) + devices = self._resolve_device_paths() strict = self._compatibility == LandLockCompatibility.HARD_REQUIREMENT - Landlock(strict=strict) \ + sandbox = Landlock(strict=strict) \ .allow_all_scope() \ .allow_all_network() \ .add_path_rule('/', access=AccessFs.EXECUTE) \ .add_path_rule(*rwd, access=FileSystemPolicy.READ_WRITE_DIR_ACCESS) \ .add_path_rule(*rwf, access=FileSystemPolicy.READ_WRITE_FILE_ACCESS) \ .add_path_rule(*rod, access=FileSystemPolicy.READ_ONLY_DIR_ACCESS) \ - .add_path_rule(*rof, access=FileSystemPolicy.READ_ONLY_FILE_ACCESS) \ - .apply() + .add_path_rule(*rof, access=FileSystemPolicy.READ_ONLY_FILE_ACCESS) + + if devices: + sandbox.add_path_rule(*devices, access=FileSystemPolicy.DEVICE_ACCESS) + + sandbox.apply() logger.info("Policy applied") diff --git a/profile/policy.yaml b/profile/policy.yaml index 22c3d2405..c421bcdca 100644 --- a/profile/policy.yaml +++ b/profile/policy.yaml @@ -21,5 +21,12 @@ filesystem_policy: - /opt/sentence_transformers - /var/tmp - /dev/shm + device_access: + - /dev/dxg + - /dev/nvidiactl + - /dev/nvidia[0-9]* + - /dev/nvidia-uvm + - /dev/nvidia-uvm-tools + - /dev/nvidia-caps landlock: compatibility: best_effort From b139a2bde4b499e04172b876ed43396985c2974a Mon Sep 17 00:00:00 2001 From: Paul Vasiletz Date: Tue, 15 Sep 2026 17:19:14 +0300 Subject: [PATCH 2/4] GPU, Landlock | add docs, add access to /proc for CUDA libcuda --- docs/README.md | 1 + docs/reference-gpu.md | 107 ++++++++++++++++++++++++++++++++++++++++++ profile/policy.py | 5 +- profile/policy.yaml | 4 ++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 docs/reference-gpu.md diff --git a/docs/README.md b/docs/README.md index ed63d60ec..9c015a75f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,6 +58,7 @@ User-facing MeTTa skills the agent invokes. Each page follows the template **Sig - [reference-channels.md](./reference-channels.md) — IRC, Telegram, Slack, Mattermost, WebSocket, and websearch adapters plus the channel contract - [reference-python-bridges.md](./reference-python-bridges.md) — `lib_llm_ext.py`, `src/helper.py`, `src/skills.pl` - [reference-memory-portability.md](./reference-memory-portability.md) — Operator backup, restore, and archive-transfer workflow +- [reference-gpu.md](./reference-gpu.md) - Experimental NVIDIA GPU access: host setup, building and running a GPU-enabled container ### Plugin API diff --git a/docs/reference-gpu.md b/docs/reference-gpu.md new file mode 100644 index 000000000..50bf7c8e4 --- /dev/null +++ b/docs/reference-gpu.md @@ -0,0 +1,107 @@ +# Reference - GPU Support (Experimental) + +> **Experimental.** GPU support is under testing and is not included in Omega +> releases yet; it is planned for a future release. Released images and +> `scripts/omega start` do not give the agent GPU access. To try it, build the +> image from the `devices-landlock-fix` branch and start the container with +> your own `docker run` command, as described below. + +## Host setup + +### Native Linux + +1. Install the NVIDIA driver for your distribution and reboot. `nvidia-smi -L` + on the host must list your GPUs. +2. Install the + [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). +3. Register the toolkit with Docker and restart Docker: + + ```sh + sudo nvidia-ctk runtime configure --runtime=docker + sudo systemctl restart docker + ``` + +4. Check that CDI is enabled in Docker: `docker info --format '{{json .CDISpecDirs}}'` + must print a non-empty list. If it does not, enable it and restart Docker: + + ```sh + sudo nvidia-ctk runtime configure --runtime=docker --cdi.enabled=true + sudo systemctl restart docker + ``` + +5. Check the CDI specification: `nvidia-ctk cdi list` must print + `nvidia.com/gpu=...` entries. Recent toolkit versions keep it up to date with + `nvidia-cdi-refresh.service`; otherwise generate it: + + ```sh + sudo nvidia-ctk cdi generate --output=/var/run/cdi/nvidia.yaml + ``` + + Regenerate the specification after every driver update. + +### Windows with WSL2 + +1. Install the NVIDIA driver on Windows. Do not install a Linux NVIDIA driver + inside the WSL distribution: WSL exposes the Windows driver as `/dev/dxg` + and the libraries in `/usr/lib/wsl/lib`. +2. Install Docker Engine and the NVIDIA Container Toolkit inside the WSL + distribution and follow steps 2-5 of the native Linux setup. + +### SELinux (Fedora, RHEL and derivatives) + +When Docker runs with SELinux labeling, containers may be denied access to the +GPU device nodes even though the nodes exist, and `nvidia-smi` fails inside the +container. Allow containers to use devices: + +```sh +sudo setsebool -P container_use_devices on +``` + +### Other setups + +- **Rootless Docker** needs extra toolkit configuration, see the rootless mode + section of the NVIDIA Container Toolkit install guide. +- **Docker Desktop** and **Podman** are not covered by these instructions. + +## Build the image + +Build the image from the `devices-landlock-fix` branch. Its security policy +allows the agent to use the GPU; images built from other branches do not. + +```sh +git clone https://github.com/singnet/Omega.git +cd Omega +git checkout devices-landlock-fix +docker build -t omega:gpu . +``` + +## Start the container + +Start the container with your own command and pass the GPUs with +`--device nvidia.com/gpu=all`, or a single GPU by its CDI name from +`nvidia-ctk cdi list`, for example `--device nvidia.com/gpu=0`. The other +options match the ones `scripts/omega` uses. Example for Telegram and OpenAI, +with `OPENAI_API_KEY`, `TG_BOT_TOKEN` and `OMEGA_AUTH_SECRET` exported in your +shell: + +```sh +docker rm -f omega +docker run -d -it \ + --name omega \ + --init \ + --security-opt no-new-privileges:true \ + --add-host=host.docker.internal:host-gateway \ + --tmpfs /tmp:size=64m,mode=1777 \ + --tmpfs /var/tmp:size=64m,mode=1777 \ + --tmpfs /run:size=16m,mode=755 \ + --volume omega-memory:/PeTTa/repos/Omega/memory \ + --device nvidia.com/gpu=all \ + -e OPENAI_API_KEY \ + -e TG_BOT_TOKEN \ + -e OMEGA_AUTH_SECRET \ + omega:gpu +``` + +Do not restart this container with `scripts/omega start`: it recreates the +container without the GPU options. `docker stop omega` and `docker start omega` +keep them. diff --git a/profile/policy.py b/profile/policy.py index 538cd13a0..cb85bc943 100644 --- a/profile/policy.py +++ b/profile/policy.py @@ -80,6 +80,7 @@ def __init__(self): self._read_only = [] self._read_write = [] self._device_access = [] + self._read_write_files = [] def load_file(self, path: str|Path): logger.info(f"Loading policy from file {path}") @@ -119,6 +120,7 @@ def load_dict(self, policy: dict): self._read_only = [Path(f'{p}') for p in ro] self._read_write = [Path(f'{p}') for p in rw] self._device_access = list(fs.get('device_access', []) or []) if fs else [] + self._read_write_files = [Path(p) for p in (fs.get('read_write_files') or [])] if fs else [] def _resolve_device_paths(self) -> list[Path]: """Expand device_access glob patterns to existing device files. @@ -150,7 +152,8 @@ def apply(self): .add_path_rule(*rwd, access=FileSystemPolicy.READ_WRITE_DIR_ACCESS) \ .add_path_rule(*rwf, access=FileSystemPolicy.READ_WRITE_FILE_ACCESS) \ .add_path_rule(*rod, access=FileSystemPolicy.READ_ONLY_DIR_ACCESS) \ - .add_path_rule(*rof, access=FileSystemPolicy.READ_ONLY_FILE_ACCESS) + .add_path_rule(*rof, access=FileSystemPolicy.READ_ONLY_FILE_ACCESS) \ + .add_path_rule(*self._read_write_files, access=FileSystemPolicy.READ_WRITE_FILE_ACCESS) if devices: sandbox.add_path_rule(*devices, access=FileSystemPolicy.DEVICE_ACCESS) diff --git a/profile/policy.yaml b/profile/policy.yaml index c421bcdca..052b32eb7 100644 --- a/profile/policy.yaml +++ b/profile/policy.yaml @@ -21,6 +21,10 @@ filesystem_policy: - /opt/sentence_transformers - /var/tmp - /dev/shm + # Existing files here may be written and truncated, but not created or removed. + # The CUDA driver names its threads via /proc/self/task//comm. + read_write_files: + - /proc device_access: - /dev/dxg - /dev/nvidiactl From ab4acedfeaccbf4352e6f4705a8809a6274f57b0 Mon Sep 17 00:00:00 2001 From: Paul Vasiletz Date: Mon, 28 Sep 2026 13:43:02 +0300 Subject: [PATCH 3/4] landlock | category merging, valid OpenShell schema --- profile/policy.py | 53 ++++++++++++++++++++------------------------- profile/policy.yaml | 12 +++++----- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/profile/policy.py b/profile/policy.py index cb85bc943..eb11ce962 100644 --- a/profile/policy.py +++ b/profile/policy.py @@ -1,6 +1,5 @@ import os import enum -import glob import yaml import json from py_landlock import Landlock, AccessFs @@ -72,15 +71,12 @@ class FileSystemPolicy: | AccessFs.REMOVE_DIR | AccessFs.MAKE_FIFO | AccessFs.MAKE_SOCK) READ_WRITE_FILE_ACCESS = (AccessFs.READ_FILE | AccessFs.WRITE_FILE | - AccessFs.TRUNCATE) - DEVICE_ACCESS = AccessFs.READ_FILE | AccessFs.WRITE_FILE | AccessFs.IOCTL_DEV + AccessFs.TRUNCATE | AccessFs.IOCTL_DEV) def __init__(self): self._compatibility = LandLockCompatibility.BEST_EFFORT self._read_only = [] self._read_write = [] - self._device_access = [] - self._read_write_files = [] def load_file(self, path: str|Path): logger.info(f"Loading policy from file {path}") @@ -119,33 +115,37 @@ def load_dict(self, policy: dict): rw.append(os.getcwd()) self._read_only = [Path(f'{p}') for p in ro] self._read_write = [Path(f'{p}') for p in rw] - self._device_access = list(fs.get('device_access', []) or []) if fs else [] - self._read_write_files = [Path(p) for p in (fs.get('read_write_files') or [])] if fs else [] - def _resolve_device_paths(self) -> list[Path]: - """Expand device_access glob patterns to existing device files. + @staticmethod + def _existing_paths(paths: list[Path]) -> list[Path]: + """Drop policy paths that do not exist on a host machine. - Patterns that match nothing on the current platform (e.g. NVIDIA - device nodes on WSL2, or /dev/dxg on bare Linux) are skipped, so the - same policy file works unmodified on both. + Landlock cannot add a rule for a missing path, and the policy lists + platform-specific paths such as GPU device nodes that exist only on + some host machines. + + Args: + paths: Paths listed in the policy. Returns: - Resolved, deduplicated paths of device files that actually exist. + Paths that exist on a host machine. """ - resolved = set() - for pattern in self._device_access: - resolved.update(Path(match).resolve() for match in glob.glob(pattern)) - return list(resolved) + existing = [p for p in paths if p.exists()] + missing = [str(p) for p in paths if not p.exists()] + if missing: + logger.info(f"Skipped missing policy paths: {missing}") + return existing def apply(self): - rod = list(filter(lambda p: p.is_dir(), self._read_only)) - rof = list(filter(lambda p: not p.is_dir(), self._read_only)) - rwd = list(filter(lambda p: p.is_dir(), self._read_write)) - rwf = list(filter(lambda p: not p.is_dir(), self._read_write)) - devices = self._resolve_device_paths() + ro = self._existing_paths(self._read_only) + rw = self._existing_paths(self._read_write) + rod = list(filter(lambda p: p.is_dir(), ro)) + rof = list(filter(lambda p: not p.is_dir(), ro)) + rwd = list(filter(lambda p: p.is_dir(), rw)) + rwf = list(filter(lambda p: not p.is_dir(), rw)) strict = self._compatibility == LandLockCompatibility.HARD_REQUIREMENT - sandbox = Landlock(strict=strict) \ + Landlock(strict=strict) \ .allow_all_scope() \ .allow_all_network() \ .add_path_rule('/', access=AccessFs.EXECUTE) \ @@ -153,11 +153,6 @@ def apply(self): .add_path_rule(*rwf, access=FileSystemPolicy.READ_WRITE_FILE_ACCESS) \ .add_path_rule(*rod, access=FileSystemPolicy.READ_ONLY_DIR_ACCESS) \ .add_path_rule(*rof, access=FileSystemPolicy.READ_ONLY_FILE_ACCESS) \ - .add_path_rule(*self._read_write_files, access=FileSystemPolicy.READ_WRITE_FILE_ACCESS) - - if devices: - sandbox.add_path_rule(*devices, access=FileSystemPolicy.DEVICE_ACCESS) - - sandbox.apply() + .apply() logger.info("Policy applied") diff --git a/profile/policy.yaml b/profile/policy.yaml index 052b32eb7..c2687b6a2 100644 --- a/profile/policy.yaml +++ b/profile/policy.yaml @@ -5,7 +5,6 @@ filesystem_policy: - /bin - /usr - /lib - - /proc - /dev/urandom - /etc - /opt @@ -21,16 +20,17 @@ filesystem_policy: - /opt/sentence_transformers - /var/tmp - /dev/shm - # Existing files here may be written and truncated, but not created or removed. # The CUDA driver names its threads via /proc/self/task//comm. - read_write_files: - /proc - device_access: + # GPU device nodes: /dev/dxg on WSL2, /dev/nvidia* on native Linux. + # Paths missing on the host are skipped. - /dev/dxg - /dev/nvidiactl - - /dev/nvidia[0-9]* + - /dev/nvidia0 + - /dev/nvidia1 + - /dev/nvidia2 + - /dev/nvidia3 - /dev/nvidia-uvm - /dev/nvidia-uvm-tools - - /dev/nvidia-caps landlock: compatibility: best_effort From c40221f45b5483cfbc8a3c0dc8621eee6cd0c329 Mon Sep 17 00:00:00 2001 From: Paul Vasiletz Date: Mon, 28 Sep 2026 14:17:31 +0300 Subject: [PATCH 4/4] landlock, NVIDIA | replace specific devices list with matcher --- profile/policy.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/profile/policy.yaml b/profile/policy.yaml index c2687b6a2..45bd1b4a0 100644 --- a/profile/policy.yaml +++ b/profile/policy.yaml @@ -26,10 +26,7 @@ filesystem_policy: # Paths missing on the host are skipped. - /dev/dxg - /dev/nvidiactl - - /dev/nvidia0 - - /dev/nvidia1 - - /dev/nvidia2 - - /dev/nvidia3 + - /dev/nvidia[0-9]* - /dev/nvidia-uvm - /dev/nvidia-uvm-tools landlock: