From 89be91770c73eee165bd5f6ea49b63f6425ad548 Mon Sep 17 00:00:00 2001 From: ndrpp Date: Tue, 13 Jan 2026 11:59:59 +0200 Subject: [PATCH 1/4] feat: update node quickstart script with gpu auto detect --- scripts/list_gpus.sh | 109 +++++++++++++++++++++++++++++++ scripts/ocean-node-quickstart.sh | 87 +++++++++++++++++++++++- 2 files changed, 193 insertions(+), 3 deletions(-) create mode 100755 scripts/list_gpus.sh diff --git a/scripts/list_gpus.sh b/scripts/list_gpus.sh new file mode 100755 index 000000000..675eb878d --- /dev/null +++ b/scripts/list_gpus.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +# Function to check for NVIDIA GPUs +get_nvidia_gpus() { + if command -v nvidia-smi &> /dev/null; then + # Query nvidia-smi for GPU count, names, and UUIDs + # We use csv format for easier parsing + nvidia-smi --query-gpu=name,uuid --format=csv,noheader | while IFS=, read -r name uuid; do + # Trim leading/trailing whitespace + name=$(echo "$name" | xargs) + uuid=$(echo "$uuid" | xargs) + + # Create a JSON object for this GPU + # Note: We use the UUID as the ID locally, but it will be aggregated later + jq -c -n \ + --arg name "$name" \ + --arg uuid "$uuid" \ + '{ + description: $name, + init: { + deviceRequests: { + Driver: "nvidia", + DeviceIDs: [$uuid] + } + } + }' + done + fi +} + +# Function to check for other GPUs (AMD, Intel, etc.) via lspci +get_generic_gpus() { + # Check if lspci is available + if ! command -v lspci &> /dev/null; then + return + fi + + # Iterate over VGA and 3D controllers + lspci -mm -n -d ::0300 | while read -r line; do process_pci_line "$line"; done + lspci -mm -n -d ::0302 | while read -r line; do process_pci_line "$line"; done +} + +process_pci_line() { + line="$1" + + slot=$(echo "$line" | awk '{print $1}') + vendor_id=$(echo "$line" | awk '{print $3}' | tr -d '"') + + # We want to exclude NVIDIA here if we already handled them via nvidia-smi. + if [[ "$vendor_id" == "10de" ]] && command -v nvidia-smi &> /dev/null; then + return + fi + + # Get human readable name + full_info=$(lspci -s "$slot" -vmm) + vendor_name=$(echo "$full_info" | grep "^Vendor:" | cut -f2-) + device_name=$(echo "$full_info" | grep "^Device:" | cut -f2-) + + description="$vendor_name $device_name" + pci_id="0000:$slot" + + # Determine driver + driver="" + if [[ "$vendor_id" == "1002" ]]; then # AMD + driver="amdgpu" + fi + + # Construct JSON + jq -c -n \ + --arg desc "$description" \ + --arg driver "$driver" \ + --arg pci_id "$pci_id" \ + '{ + description: $desc, + init: { + deviceRequests: { + Driver: (if $driver != "" then $driver else null end), + DeviceIDs: [$pci_id] + } + } + }' +} + +# Function to get all GPUs in JSON array format +get_all_gpus_json() { + ( + get_nvidia_gpus + get_generic_gpus + ) | jq -s ' + group_by(.description) | map({ + id: (.[0].description | ascii_downcase | gsub("[^a-z0-9]"; "-") | gsub("-+"; "-") | sub("^-"; "") | sub("-$"; "")), + description: .[0].description, + type: "gpu", + total: length, + init: { + deviceRequests: { + Driver: .[0].init.deviceRequests.Driver, + DeviceIDs: (map(.init.deviceRequests.DeviceIDs[]) | unique), + Capabilities: [["gpu"]] + } + } + }) | map(if .init.deviceRequests.Driver == null then del(.init.deviceRequests.Driver) else . end) + ' +} + +# Main execution only if script is not sourced +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + get_all_gpus_json +fi diff --git a/scripts/ocean-node-quickstart.sh b/scripts/ocean-node-quickstart.sh index 4a1ed9c97..964017dd0 100755 --- a/scripts/ocean-node-quickstart.sh +++ b/scripts/ocean-node-quickstart.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Copyright (c) 2024 Ocean Protocol contributors +# Copyright (c) 2026 Ocean Protocol contributors # SPDX-License-Identifier: Apache-2.0 # @@ -100,6 +100,13 @@ read P2P_ipV6BindWsPort P2P_ipV6BindWsPort=${P2P_ipV6BindWsPort:-9003} validate_port "$P2P_ipV6BindWsPort" +P2P_ENABLE_UPNP='false' +read -p "Enable UPnP (useful in case you can no set up port forwarding)? [ y/n ]: " enable_upnp +if [ "$enable_upnp" == "y" ]; then + P2P_ENABLE_UPNP='true' +fi + + read -p "Provide the public IPv4 address or FQDN where this node will be accessible: " P2P_ANNOUNCE_ADDRESS if [ -n "$P2P_ANNOUNCE_ADDRESS" ]; then @@ -142,9 +149,83 @@ fi # Set default compute environments if not already defined if [ -z "$DOCKER_COMPUTE_ENVIRONMENTS" ]; then echo "Setting default DOCKER_COMPUTE_ENVIRONMENTS configuration" - export DOCKER_COMPUTE_ENVIRONMENTS="[{\"socketPath\":\"/var/run/docker.sock\",\"resources\":[{\"id\":\"disk\",\"total\":10}],\"storageExpiry\":604800,\"maxJobDuration\":36000,\"minJobDuration\":60,\"fees\":{\"1\":[{\"feeToken\":\"0x123\",\"prices\":[{\"id\":\"cpu\",\"price\":1}]}]},\"free\":{\"maxJobDuration\":360000,\"minJobDuration\":60,\"maxJobs\":3,\"resources\":[{\"id\":\"cpu\",\"max\":1},{\"id\":\"ram\",\"max\":1},{\"id\":\"disk\",\"max\":1}]}}]" + export DOCKER_COMPUTE_ENVIRONMENTS='[ + { + "socketPath": "/var/run/docker.sock", + "resources": [ + { + "id": "disk", + "total": 10 + } + ], + "storageExpiry": 604800, + "maxJobDuration": 36000, + "minJobDuration": 60, + "fees": { + "1": [ + { + "feeToken": "0x123", + "prices": [ + { + "id": "cpu", + "price": 1 + } + ] + } + ] + }, + "free": { + "maxJobDuration": 360000, + "minJobDuration": 60, + "maxJobs": 3, + "resources": [ + { + "id": "cpu", + "max": 1 + }, + { + "id": "ram", + "max": 1 + }, + { + "id": "disk", + "max": 1 + } + ] + } + } + ]' fi +# GPU Detection and Integration +LIST_GPUS_SCRIPT="$(dirname "$0")/list_gpus.sh" +if [ -f "$LIST_GPUS_SCRIPT" ] && command -v jq &> /dev/null; then + echo "Checking for GPUs..." + source "$LIST_GPUS_SCRIPT" + DETECTED_GPUS=$(get_all_gpus_json) + + # Check if we got any GPUs (array not empty) + GPU_COUNT=$(echo "$DETECTED_GPUS" | jq 'length') + + if [ "$GPU_COUNT" -gt 0 ]; then + echo "Detected $GPU_COUNT GPU type(s). Updating configuration..." + + # Merge detected GPUs into the resources array of the first environment + # We use jq to append the detected GPU objects to existing resources + DOCKER_COMPUTE_ENVIRONMENTS=$(echo "$DOCKER_COMPUTE_ENVIRONMENTS" | jq --argjson gpus "$DETECTED_GPUS" '.[0].resources += $gpus') + + # Also update free resources to include GPUs if desired, or at least the pricing? + # For now, let's just ensure they are in the available resources list. + echo "GPUs added to Compute Environment resources." + else + echo "No GPUs detected." + fi +else + echo "Skipping GPU detection (script not found or jq missing)." +fi + +echo $DOCKER_COMPUTE_ENVIRONMENTS + cat < docker-compose.yml services: ocean-node: @@ -205,7 +286,7 @@ services: # P2P_mDNSInterval: '' # P2P_connectionsMaxParallelDials: '' # P2P_connectionsDialTimeout: '' -# P2P_ENABLE_UPNP: '' + P2P_ENABLE_UPNP: '$P2P_ENABLE_UPNP' # P2P_ENABLE_AUTONAT: '' # P2P_ENABLE_CIRCUIT_RELAY_SERVER: '' # P2P_ENABLE_CIRCUIT_RELAY_CLIENT: '' From ea3cdf8358eb274cfa847635a9224a1ce195c554 Mon Sep 17 00:00:00 2001 From: ndrpp Date: Tue, 13 Jan 2026 15:49:00 +0200 Subject: [PATCH 2/4] feat: update README for quickstart script --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 705261d69..e5137efe1 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,11 @@ npm run quickstart This command will run you through the process of setting up the environmental variables for your node. +> [!NOTE] +> The quickstart script attempts to automatically detect GPUs (NVIDIA via `nvidia-smi`, others via `lspci`) and appends them to your `DOCKER_COMPUTE_ENVIRONMENTS`. +> If you choose to manually configure `DOCKER_COMPUTE_ENVIRONMENTS` before running the script (e.g. via environment variable), be aware that auto-detected GPUs will be **merged** into your configuration, which could lead to duplication if you already manually defined them. +> For most users, it is recommended to let the script handle GPU detection automatically. + ## Option 3: Running Ocean Nodes with PM2 PM2 is a process manager that makes it easy to manage and monitor your Node.js applications. From 12e6154e4cec85b19edcec75d4857b56b1b426b5 Mon Sep 17 00:00:00 2001 From: ndrpp Date: Mon, 19 Jan 2026 12:13:33 +0200 Subject: [PATCH 3/4] feat: ensure jq is installed --- scripts/ocean-node-quickstart.sh | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/scripts/ocean-node-quickstart.sh b/scripts/ocean-node-quickstart.sh index 964017dd0..e4988322f 100755 --- a/scripts/ocean-node-quickstart.sh +++ b/scripts/ocean-node-quickstart.sh @@ -52,6 +52,69 @@ validate_ip_or_fqdn() { return 0 } +ensure_jq() { + + if command -v jq >/dev/null 2>&1; then + echo "jq is already installed." + return 0 + fi + + echo "jq not found. Attempting to install..." + + if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" + else + SUDO="" + fi + + if [ -f /etc/os-release ]; then + . /etc/os-release + case "$ID" in + debian|ubuntu|linuxmint|pop|kali) + $SUDO apt-get update && $SUDO apt-get install -y jq + ;; + fedora) + $SUDO dnf install -y jq + ;; + centos|rhel|almalinux|rocky) + if command -v dnf >/dev/null; then + $SUDO dnf install -y epel-release + $SUDO dnf install -y jq + else + $SUDO yum install -y epel-release + $SUDO yum install -y jq + fi + ;; + alpine) + $SUDO apk add jq + ;; + arch|manjaro) + $SUDO pacman -Sy --noconfirm jq + ;; + opensuse*|sles) + $SUDO zypper install -y jq + ;; + *) + echo "Error: Unsupported distribution '$ID'. Please install jq manually." + return 1 + ;; + esac + else + echo "Error: Cannot detect OS distribution. Please install jq manually." + return 1 + fi + + if command -v jq >/dev/null 2>&1; then + echo "jq installed successfully." + return 0 + else + echo "Error: Failed to install jq." + return 1 + fi +} + +echo "Checking prerequisites (jq) are installed.." +ensure_jq read -p "Do you have your private key for running the Ocean Node [ y/n ]: " has_key @@ -68,6 +131,7 @@ else echo "Generating Private Key, please wait..." output=$(head -c 32 /dev/urandom | xxd -p | tr -d '\n' | awk '{print "0x" $0}') PRIVATE_KEY=$(echo "$output") + echo -e "Generated Private Key: \e[1;31m$PRIVATE_KEY\e[0m" validate_hex "$PRIVATE_KEY" fi From 1ef6cdda628923790e1c858964d83051296d72c3 Mon Sep 17 00:00:00 2001 From: ndrpp Date: Mon, 19 Jan 2026 12:16:33 +0200 Subject: [PATCH 4/4] feat: enrich gpu information using sysfs --- scripts/list_gpus.sh | 168 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 13 deletions(-) diff --git a/scripts/list_gpus.sh b/scripts/list_gpus.sh index 675eb878d..1b7510fcb 100755 --- a/scripts/list_gpus.sh +++ b/scripts/list_gpus.sh @@ -20,7 +20,7 @@ get_nvidia_gpus() { init: { deviceRequests: { Driver: "nvidia", - DeviceIDs: [$uuid] + Devices: [$uuid] } } }' @@ -28,6 +28,35 @@ get_nvidia_gpus() { fi } +# Declare the associative array (hashmap) globally +declare -A gpu_map + +map_pci_to_primary() { + # Iterate over all card nodes in /sys/class/drm + # We filter for 'card*' to ignore 'renderD*' nodes for the primary map + for card_path in /sys/class/drm/card*; do + + # logical check to ensure the glob matched a file + [ -e "$card_path" ] || continue + + # Resolve the symlink to the actual PCI device directory + # Example result: /sys/devices/pci0000:00/.../0000:03:00.0 + real_device_path=$(readlink -f "$card_path/device") + + # The last part of that path is the PCI ID (e.g., 0000:03:00.0) + pci_id=$(basename "$real_device_path") + + # The last part of the card_path is the card name (e.g., card0) + card_name=$(basename "$card_path") + + # Store in the hashmap + # Key: PCI ID, Value: /dev/dri/cardX + gpu_map["$pci_id"]="/dev/dri/$card_name" + done +} + + + # Function to check for other GPUs (AMD, Intel, etc.) via lspci get_generic_gpus() { # Check if lspci is available @@ -35,6 +64,7 @@ get_generic_gpus() { return fi + map_pci_to_primary # Iterate over VGA and 3D controllers lspci -mm -n -d ::0300 | while read -r line; do process_pci_line "$line"; done lspci -mm -n -d ::0302 | while read -r line; do process_pci_line "$line"; done @@ -44,10 +74,10 @@ process_pci_line() { line="$1" slot=$(echo "$line" | awk '{print $1}') - vendor_id=$(echo "$line" | awk '{print $3}' | tr -d '"') + vendor_id_hex=$(echo "$line" | awk '{print $3}' | tr -d '"') # We want to exclude NVIDIA here if we already handled them via nvidia-smi. - if [[ "$vendor_id" == "10de" ]] && command -v nvidia-smi &> /dev/null; then + if [[ "$vendor_id_hex" == "10de" ]] && command -v nvidia-smi &> /dev/null; then return fi @@ -61,24 +91,130 @@ process_pci_line() { # Determine driver driver="" - if [[ "$vendor_id" == "1002" ]]; then # AMD - driver="amdgpu" + if [[ "$vendor_id_hex" == "1002" ]]; then # AMD + driver="amdgpu" + elif [[ "$vendor_id_hex" = "8086" ]]; then # Intel + driver="intel" + fi + + device_id="" + card_path="" + if [ -n "${gpu_map[$pci_id]}" ]; then + # Get device id from /sys/class/drm map + device_id="${gpu_map[$pci_id]}" # e.g. /dev/dri/card0 + # Reconstruct sysfs card path from the device path + # device_id is /dev/dri/cardX, we want /sys/class/drm/cardX + card_name=$(basename "$device_id") + card_path="/sys/class/drm/$card_name" + else + # If it can't be found, default to pci id + device_id="${pci_id}" + fi + + local devices=() + local binds=() + local cap_add=() + local group_add=() + local ipc_mode="null" + local shm_size="null" + local security_opt="null" + + # Only perform detailed checks if we found the card path + if [ -n "$card_path" ] && [ -e "$card_path" ]; then + + # Resolve real device path for getting sibling render node + local real_device_path=$(readlink -f "$card_path/device") + local render_name="" + if [ -d "$real_device_path/drm" ]; then + render_name=$(ls "$real_device_path/drm" | grep "^renderD" | head -n 1) + fi + + case "$vendor_id_hex" in + "1002") # AMD (0x1002) + # Devices + [ -e "/dev/dxg" ] && devices+=("/dev/dxg") + devices+=("$device_id") # /dev/dri/cardX + + # Binds + [ -e "/usr/lib/wsl/lib/libdxcore.so" ] && \ + binds+=("/usr/lib/wsl/lib/libdxcore.so:/usr/lib/libdxcore.so") + [ -e "/opt/rocm/lib/libhsa-runtime64.so.1" ] && \ + binds+=("/opt/rocm/lib/libhsa-runtime64.so.1:/opt/rocm/lib/libhsa-runtime64.so.1") + + # Configs + cap_add+=("SYS_PTRACE") + ipc_mode="\"host\"" + shm_size="8589934592" + # SecurityOpt is a JSON object + security_opt='{"seccomp": "unconfined"}' + ;; + + "8086") # Intel (0x8086) + # Devices + [ -n "$render_name" ] && devices+=("/dev/dri/$render_name") + devices+=("$device_id") + + # Configs + group_add+=("video" "render") + cap_add+=("SYS_ADMIN") + ;; + esac + else + # Fallback if we don't have the card path mapped, but still want to add the primary device if applicable + # This preserves behavior for devices that might not map correctly but are enumerated + if [[ "$vendor_id_hex" == "1002" ]] || [[ "$vendor_id_hex" == "8086" ]]; then + if [[ "$device_id" == /dev/* ]]; then + devices+=("$device_id") + fi + fi + fi + + + # --- Construct JSON --- + + # Helper to convert bash arrays using jq + # (re-using the logic, but localized vars) + json_devices=$(printf '%s\n' "${devices[@]}" | jq -R . | jq -s . | jq 'map(select(length > 0))') + json_binds=$(printf '%s\n' "${binds[@]}" | jq -R . | jq -s . | jq 'map(select(length > 0))') + json_cap=$(printf '%s\n' "${cap_add[@]}" | jq -R . | jq -s . | jq 'map(select(length > 0))') + json_group=$(printf '%s\n' "${group_add[@]}" | jq -R . | jq -s . | jq 'map(select(length > 0))') + + # If Devices array is empty, ensure at least the ID we found is there (unless it was already added) + # Using 'index' to check if device_id is present is tricky with jq on the fly, + # but standardizing on what we found is safer. + # If the detailed logic above didn't populate devices (e.g. unknown vendor), we fall back to just the ID. + if [ "$(echo "$json_devices" | jq length)" -eq 0 ]; then + json_devices="[\"$device_id\"]" fi - # Construct JSON + jq -c -n \ --arg desc "$description" \ --arg driver "$driver" \ - --arg pci_id "$pci_id" \ + --arg device_id "$device_id" \ + --argjson dev "$json_devices" \ + --argjson bind "$json_binds" \ + --argjson cap "$json_cap" \ + --argjson group "$json_group" \ + --argjson sec "$security_opt" \ + --argjson shm "$shm_size" \ + --argjson ipc "$ipc_mode" \ '{ description: $desc, init: { deviceRequests: { Driver: (if $driver != "" then $driver else null end), - DeviceIDs: [$pci_id] - } + Devices: $dev, + Capabilities: [["gpu"]] + }, + Binds: $bind, + CapAdd: $cap, + GroupAdd: $group, + SecurityOpt: $sec, + ShmSize: $shm, + IpcMode: $ipc } - }' + } | del(.. | select(. == null)) | del(.. | select(. == []))' } # Function to get all GPUs in JSON array format @@ -95,10 +231,16 @@ get_all_gpus_json() { init: { deviceRequests: { Driver: .[0].init.deviceRequests.Driver, - DeviceIDs: (map(.init.deviceRequests.DeviceIDs[]) | unique), + (if .[0].init.deviceRequests.Driver == "nvidia" then "DeviceIDs" else "Devices" end): (map(.init.deviceRequests.Devices[]?) | unique), Capabilities: [["gpu"]] - } - } + }, + Binds: (map(.init.Binds[]?) | unique), + CapAdd: (map(.init.CapAdd[]?) | unique), + GroupAdd: (map(.init.GroupAdd[]?) | unique), + SecurityOpt: .[0].init.SecurityOpt, + ShmSize: .[0].init.ShmSize, + IpcMode: .[0].init.IpcMode + } | del(.. | select(. == null)) | del(.. | select(. == [])) }) | map(if .init.deviceRequests.Driver == null then del(.init.deviceRequests.Driver) else . end) ' }