Installing HORUS

Most platforms have a prebuilt, checksum-verified binary, so the usual install is one command and under a minute. This guide covers that, the source build for the platforms without one, upgrading, and uninstalling.

Platform Support

HORUS has native cross-platform support:

PlatformStatusShared Memory PathNotes
Ubuntu 20.04+Supported/dev/shm/horus_<namespace>/Recommended for production
Ubuntu 22.04+Supported/dev/shm/horus_<namespace>/Best performance
Debian 11+Supported/dev/shm/horus_<namespace>/Tested and working
Fedora 36+Supported/dev/shm/horus_<namespace>/Use dnf for packages
Arch LinuxSupported/dev/shm/horus_<namespace>/Community maintained
Raspberry PiSupported/dev/shm/horus_<namespace>/64-bit and 32-bit Pi OS both have a prebuilt binary
macOSSupported/tmp/horus_<namespace>/tmpfs-backed, full support
WindowsSupported%TEMP%\horus_<namespace>\Native Windows support
WSL 2Supported/dev/shm/horus_<namespace>/Linux mode in WSL

The namespace defaults to the literal default — so on Linux the real directory is /dev/shm/horus_default/. Set HORUS_NAMESPACE to isolate robots from each other.

Prebuilt Binaries

Each release publishes exactly these six assets, plus a SHA256SUMS file the installer verifies every download against. The list is the release workflow's matrix, and the release refuses to publish at all if one of them is missing — a hole in the set is a 404 for that platform, followed by a silent fall-through to an hours-long source build.

PlatformRelease assetNotes
Linux x86_64horus-linux-amd64.tar.gzglibc 2.28 or newer
Linux aarch64 (64-bit Pi OS, Jetson)horus-linux-arm64.tar.gzglibc 2.28 or newer
Linux armv7 (32-bit Pi OS)horus-linux-armv7.tar.gzglibc 2.28 or newer
macOS Apple Siliconhorus-macos-arm64.tar.gz
macOS Intelhorus-macos-amd64.tar.gz
Windows x86_64horus-windows-amd64.zipsee Windows

The Linux binaries are dynamically linked against glibc, not static, and they are cross-compiled against a glibc 2.28 floor — Debian 10, RHEL 8, Ubuntu 18.04, the same floor the Python wheels use. The release workflow reads the version-needs back out of each ELF and fails the release rather than publishing an asset above the floor.

Releases cut before that floor was enforced — v0.4.0 and earlier — were built natively on the CI runner and carry a GLIBC_2.39 version-need, so they will not start on Raspberry Pi OS, JetPack, Ubuntu 22.04, Debian 12 or RHEL 9. On those, build from source until a newer release is available.

Anything at or above the floor runs the binaries. Anything older, or a musl distro such as Alpine, needs a source build:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_BUILD_FROM_SOURCE=1 bash
ℹ️Check the floor before you blame the install

horus: /lib/.../libc.so.6: version 'GLIBC_2.xx' not found means exactly this and nothing else. The installer runs horus --version before it declares success, so it catches the case and tells you to build from source rather than reporting a working install you cannot run. If you see the message from a binary you copied onto a robot by hand, that check never ran.

Any platform with no asset — a BSD, a musl distro, an architecture not in the table — is not an error path. The installer prints No pre-built binary for <os>-<arch> and compiles the same tag it would have downloaded.

Prerequisites

For any install:

  • Operating System: Linux, macOS, or Windows
    • Linux: glibc 2.28 or newer for a prebuilt binary; Ubuntu 20.04+ recommended
    • macOS: Native support using /tmp/horus_<namespace>/
    • Windows: Native support using %TEMP%\horus_<namespace>\
  • curl and git — the installer fetches the matching source tree with git, and keeps it
  • Internet connection: to reach GitHub Releases

Only for a source build (a platform with no release asset, HORUS_BUILD_FROM_SOURCE=1, or a glibc below the floor):

  • Rust 1.90+: the workspace MSRV. We'll install this in Step 1
  • Build Tools & System Libraries: Build tools, pkg-config, and all required development libraries
    # Ubuntu/Debian/Raspberry Pi OS - COMPLETE dependencies (copy-paste this!)
    sudo apt update
    sudo apt install -y build-essential pkg-config \
      libssl-dev libudev-dev libasound2-dev \
      libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
      libwayland-dev wayland-protocols libxkbcommon-dev \
      libvulkan-dev libfontconfig-dev libfreetype-dev \
      libv4l-dev
    
    # Fedora/RHEL/CentOS
    sudo dnf groupinstall "Development Tools"
    sudo dnf install -y pkg-config openssl-devel systemd-devel alsa-lib-devel \
      libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel \
      wayland-devel wayland-protocols-devel libxkbcommon-devel \
      vulkan-devel fontconfig-devel freetype-devel \
      libv4l-devel
    
    # Arch Linux
    sudo pacman -S base-devel pkg-config openssl systemd alsa-lib \
      libx11 libxrandr libxi libxcursor libxinerama \
      wayland wayland-protocols libxkbcommon \
      vulkan-icd-loader fontconfig freetype2 \
      v4l-utils
    
  • 10-15 minutes: For a first-time source build

What these packages do:

  • Core: libssl-dev (networking), libudev-dev (device detection), libasound2-dev (audio)
  • Graphics/GUI: X11, Wayland libraries (required for monitor)
  • Optional: libv4l-dev (camera support), fontconfig (improved text rendering)

Not required:

  • Rust, on any platform with a prebuilt binary — see Prebuilt Binaries
  • A clone of the repository; the installer fetches its own source
  • Systems programming experience

One line, no clone, no Rust toolchain needed first:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash
horus --help

If the help command displays available commands, installation is complete. Skip to Next Steps.

What the installer does:

  1. Resolves the latest release tag — call it T
  2. Downloads the prebuilt binary for T and verifies it against that release's SHA256SUMS, refusing to install if the file is missing, has no line for your asset, or the digest does not match
  3. Clones the source at the same T into ~/.horus/cache/horus@<version>/, and compiles horus_manager from it if no binary matched your platform (~3-5 min)
  4. Installs the horus binary to ~/.cargo/bin/ (or ~/.local/bin/), and adds that directory to your PATH
  5. Runs horus --version and refuses to report success unless it works
  6. Records what it installed in ~/.horus/installed_version and ~/.horus/install_manifest.toml
ℹ️Why the source tree stays on disk

horus run and horus build generate a Cargo.toml that depends on horus by path, pointing at ~/.horus/cache/horus@<version>/. Deleting that directory leaves a CLI that cannot compile a single Rust project — see HORUS source not found.

That is also why the binary and the source come from one resolved tag rather than two refs. When they did not, both trees called themselves 0.4.0 while their shared-memory headers disagreed, and the symptom arrived hours later as horus topic list reporting 0 messages on a live topic.

Installer Options

Every knob is an environment variable. The installer reads them from its own environment, so with a pipe they go on the right-hand side — VAR=x curl ... | bash puts the variable in curl's environment, where nothing reads it:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_VERSION=v0.4.0 bash
VariableEffect
HORUS_VERSION=v0.4.0Install exactly that release — binary and source. The leading v is optional
HORUS_BUILD_FROM_SOURCE=1Skip the prebuilt binary; compile the source at the resolved tag
HORUS_LOCAL_SOURCE=/pathAir-gapped: build a source tree already on disk. No clone, nothing fetched for the source
HORUS_PREFIX=/opt/horusInstall root override: $HORUS_PREFIX/bin for the binary, $HORUS_PREFIX in place of ~/.horus for state and cache. Also how a root install says where it means to go. horus run searches $HORUS_PREFIX/cache first, then ~/.cache/horus and ~/.horus/cache — so keep HORUS_PREFIX exported in the environment of whoever runs horus, or set HORUS_SOURCE=$HORUS_PREFIX/cache/horus@<version> instead
HORUS_INSTALL_BRANCH=devDeveloper escape hatch: build from a branch. Forces a source build
HORUS_NO_SHELL_INTEGRATION=1Leave shell rc files alone — skips horus env --init, which shadows cargo, pip, cmake, conan and vcpkg inside HORUS projects

Full descriptions are in the environment variable reference.

⚠️`HORUS_INSTALL_BRANCH` never pairs with a release binary

Pinning a branch used to change only which source was cloned, while the binary still came from releases/latest — so the one documented way to "pin" an install was the surest way to end up with a CLI and libraries from different commits. It now implies HORUS_BUILD_FROM_SOURCE=1: a branch has no matching release asset, so the whole install is compiled from that branch or not made at all.

To pin a release, use HORUS_VERSION. That is the one that moves both halves together.

Installing as root, or for another user

curl ... | sudo bash is how a provisioning runbook drives a shell installer, and $HOME is /root there. The installer resolves $SUDO_USER and installs into that account's home instead. With no SUDO_USER — the normal case under Docker, systemd and Ansible's become — it stops and asks you to say where, rather than putting the binary, the cache and the state under /root and printing "Installation complete!":

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_PREFIX=/opt/horus bash

Shell integration and completions are skipped for a root-on-behalf-of install, because both write to $HOME and under sudo that is root's. Run horus env --init as the target user afterwards if you want the tool proxies.

Installing from a clone

If you already have the repository — or you are working on HORUS itself:

git clone https://github.com/softmata/horus.git
cd horus
./install.sh

This is the same script, so it still resolves and installs a release tag. To install the checkout you are standing in, point it at the tree:

HORUS_LOCAL_SOURCE="$PWD" ./install.sh

Step-by-Step Installation

The one-liner above needs only curl and git. This longer route installs the prerequisites explicitly, which is what you want when the fast path fell through to a source build — a platform with no release asset, HORUS_BUILD_FROM_SOURCE=1, or a glibc older than the floor.

Step 1: Install Rust

HORUS is built with Rust. No prior Rust experience is required; HORUS also supports Python for application development.

A source build needs Rust 1.90 or newer — the workspace MSRV. The installer reads that floor out of the workspace manifest and refuses before it starts compiling, rather than failing several minutes in with a cargo error about a package you have never heard of.

On Linux (or WSL/Docker):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the prompts (just press Enter for defaults).

Restart your terminal or run:

source $HOME/.cargo/env

Verify Rust is installed:

rustc --version
cargo --version

You should see version numbers like rustc 1.90.0 or higher.

Step 2: Install Git (If You Don't Have It)

Check if you have Git:

git --version

If not, install it:

# Ubuntu/Debian
sudo apt install git

# Fedora
sudo dnf install git

# Arch Linux
sudo pacman -S git

Step 3: Fetch the Installer

You do not need a clone — the installer fetches its own source. Either pipe it:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash

or clone first, if you want to read the script before running it:

git clone https://github.com/softmata/horus.git
cd horus

Step 4: Run the Installer

./install.sh

This script:

  1. Resolves one release tag, and uses it for both the binary and the source
  2. Downloads the prebuilt horus binary for that tag and verifies it against that release's SHA256SUMS, or compiles horus_manager from the cached source if no asset matches your platform (~3-5 min)
  3. Caches the source at ~/.horus/cache/horus@<version>/
  4. Installs the horus binary to ~/.cargo/bin/ (or ~/.local/bin/)
  5. Adds the install directory to your PATH and runs horus env --init
  6. Verifies with horus --version, and compares the binary's version against the cached source before declaring success
  7. Writes ~/.horus/installed_version and ~/.horus/install_manifest.toml

The output displays progress with colored indicators. Green checkmarks indicate successful steps.

It also installs shell completions for bash, zsh or fish, and a man page (man horus). Both are best-effort: a failure there is reported but never fails the install.

Step 5: Verify Installation

Test that the horus command works:

horus --help

You should see a list of available commands like new, run, monitor, etc.

Python Support

The Python bindings are a separate distribution: install.sh installs the CLI and the source cache, never the Python package. The supported way to get them is pip, with a version floor:

pip install "horus-robotics>=0.4.0"
⚠️The floor is deliberate, and today it errors

The newest horus-robotics on PyPI is 0.1.9. It imports fine and runs fine, and it speaks an older shared-memory format than a 0.4.x CLI — so a bare pip install horus-robotics gets you a package whose nodes publish topics your CLI cannot see, with no error anywhere. horus topic list simply reports nothing.

Keeping the >=0.4.0 floor turns that into a resolver error you can read instead of a silence you have to debug. Until 0.4.x is published, build the bindings from the tree the installer already cached — same tag as your CLI, by construction:

pip install ~/.horus/cache/horus@0.4.0/horus_py

Substitute your own version; horus --version prints it.

Building the bindings by hand (working on HORUS itself, or on a platform with no wheel):

# 1. Install maturin (Python build tool)
# Option A: Via Cargo (recommended for Ubuntu 24.04+)
cargo install maturin

# Option B: Via pip (if not blocked by PEP 668)
# pip install maturin

# 2. Navigate to the Python bindings in the cached source
cd ~/.horus/cache/horus@0.4.0/horus_py

# 3. Build and install into the active interpreter (takes ~3 minutes)
maturin develop --release

To verify Python bindings work:

python3 -c "import horus; print('Python bindings installed successfully')"

Note: The PyPI package name is horus-robotics, but you import it as import horus in Python code.

See Python Bindings for complete API documentation and examples.

Rust Crate Support

There is no crates.io channel. cargo add horus and cargo install horus resolve to an unrelated crate that owns the name on crates.io, and the first use horus::prelude::*; fails to compile against it. HORUS's own crates carry git and path dependencies that cargo package rejects, so none of them are published.

horus new and horus run point Cargo at ~/.horus/cache/horus@<version>/ as a path dependency instead — which is why the installer keeps that tree, and why a horus line in Cargo.toml is neither needed nor correct.

Platform-Specific Notes

Linux (Ubuntu/Debian)

Linux installation is straightforward. If build errors occur, install all required packages:

sudo apt update
sudo apt install -y build-essential pkg-config \
  libssl-dev libudev-dev libasound2-dev \
  libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
  libwayland-dev wayland-protocols libxkbcommon-dev \
  libvulkan-dev libfontconfig-dev libfreetype-dev \
  libv4l-dev

Linux (Fedora/RHEL)

sudo dnf groupinstall "Development Tools"
sudo dnf install -y pkg-config openssl-devel systemd-devel alsa-lib-devel \
  libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel \
  wayland-devel wayland-protocols-devel libxkbcommon-devel \
  vulkan-devel fontconfig-devel freetype-devel \
  libv4l-devel

Raspberry Pi

HORUS runs on Raspberry Pi. Install Ubuntu Server or Raspberry Pi OS, then:

# Install all dependencies
sudo apt update
sudo apt install -y build-essential pkg-config \
  libssl-dev libudev-dev libasound2-dev \
  libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
  libwayland-dev wayland-protocols libxkbcommon-dev \
  libvulkan-dev libfontconfig-dev libfreetype-dev \
  libv4l-dev

# Raspberry Pi specific packages (GPIO, I2C, SPI support)
sudo apt install -y libraspberrypi-dev i2c-tools python3-smbus

# Enable I2C and SPI (required for sensors)
sudo raspi-config
# Navigate to: Interface Options → I2C → Enable
# Navigate to: Interface Options → SPI → Enable

Performance tips:

  • Use 64-bit OS for better performance
  • Compile with --release flag (much faster than debug)
  • Allocate more RAM to GPU if using camera nodes (in /boot/config.txt)

NVIDIA Jetson Nano

HORUS supports Jetson Nano with GPU acceleration for vision tasks:

# Install all dependencies
sudo apt update
sudo apt install -y build-essential pkg-config \
  libssl-dev libudev-dev libasound2-dev \
  libx11-dev libxrandr-dev libxi-dev libxcursor-dev libxinerama-dev \
  libwayland-dev wayland-protocols libxkbcommon-dev \
  libvulkan-dev libfontconfig-dev libfreetype-dev \
  libv4l-dev

# Jetson specific packages (GPU acceleration)
sudo apt install -y nvidia-jetpack

# Verify CUDA is installed
nvcc --version

GPU acceleration:

  • Install JetPack for CUDA support; HORUS itself ships no CUDA feature
  • --enable <cap> forwards <cap> as a cargo feature to your crate, so horus run --enable cuda works only once your project declares a cuda feature under [rust.features] in its horus.toml (in a workspace, in the member's horus.toml — a virtual root cannot carry [features]) and your own node code gates the CUDA path on it

macOS (Native)

HORUS has native macOS support using /tmp/horus_<namespace>/ for shared memory:

# 1. Install Xcode Command Line Tools (if not already installed) — this is
#    where git comes from on a clean macOS
xcode-select --install

# 2. Install HORUS. Both Apple Silicon and Intel have a prebuilt binary, so
#    this needs no Rust toolchain.
curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash

# 3. Verify installation
horus --help

macOS shared memory:

  • Uses /tmp/horus_<namespace>/, i.e. /tmp/horus_default/ by default (tmpfs-backed, fast)
  • Automatically created on first run
  • Same API as Linux - no code changes needed

Alternative options (if native doesn't work for your use case):

  • Docker - Run HORUS in a Linux container; see Docker below
  • VMware/Parallels - Full Linux VM
  • Cloud Linux - Remote development on AWS/DigitalOcean

Windows (Native)

HORUS has native Windows support using %TEMP%\horus_<namespace>\ for shared memory.

Windows has its own installer, install.ps1. Run it from PowerShell:

irm https://github.com/softmata/horus/raw/main/install.ps1 | iex
horus --help

That downloads horus-windows-amd64.zip for the resolved release tag, verifies it against that release's SHA256SUMS, clones the matching source into ~\.horus\cache\horus@<version>, installs horus.exe into ~\.cargo\bin, and adds that directory to your user PATH. It needs git (winget install --id Git.Git); it needs a Rust toolchain only if you ask it to build from source.

⚠️Do not run install.sh under Git Bash

install.sh verifies the checksum, prints Checksum verified, and then dies at unzip: command not found (exit 127) — unzip is not in Git for Windows' bundled MSYS2 set, and the tar that is there is GNU tar, which cannot read a zip. The script now falls back to Windows' own tar.exe when it can find it and tells you what to do when it cannot, but install.ps1 is the supported path and does not have the problem at all.

Pinning a version, or any other option. A piped script cannot take parameters, so the knobs are the same environment variables install.sh uses. PowerShell has no VAR=value command prefix form, so set them as their own statement first:

$env:HORUS_VERSION = 'v0.4.0'
irm https://github.com/softmata/horus/raw/main/install.ps1 | iex

Saved to a file instead, the same knobs are real parameters:

irm https://github.com/softmata/horus/raw/main/install.ps1 -OutFile install.ps1
.\install.ps1 -Version v0.4.0

-Version, -BuildFromSource, -InstallBranch, -LocalSource, -Prefix and -NoShellIntegration correspond one-for-one to the variables in Installer Options.

ℹ️Windows PowerShell 5.1

The installer turns TLS 1.2 on for you. Without that, an unpatched Windows PowerShell 5.1 fails against github.com with "The request was aborted: Could not create SSL/TLS secure channel", which reads like a proxy problem and is not one. PowerShell 7 negotiates on its own.

Windows shared memory:

  • Uses %TEMP%\horus_<namespace>\ (typically C:\Users\<user>\AppData\Local\Temp\horus_default\)
  • Automatically created on first run
  • Cross-process communication works natively

Alternative: WSL 2 (if you prefer Linux environment)

  1. Open PowerShell as Administrator
  2. Run: wsl --install
  3. Restart your computer
  4. Follow Linux installation steps inside WSL

Hardware Driver Support (Optional)

HORUS integrates hardware drivers distributed as registry packages; none are bundled with the core install. These are completely optional - the default installation does NOT require any hardware packages or drivers.

Key points:

  • The default install works without any hardware packages
  • Nodes that declare no [hardware] need no driver packages at all; horus run --sim swaps declared hardware for stubs
  • Hardware drivers are installed from the registry only when you ask for them
  • You can install HORUS on your laptop and deploy to hardware later

When you need hardware packages:

  • Only when you install a hardware driver from the registry (horus install <driver>) and configure it under [hardware] in horus.toml
  • Only for production deployment on actual robots
  • Never needed for development, testing, or simulation

Hardware Drivers Available as Registry Packages

None of these ship with the core install. Find and install them with the horus CLI (horus search <hardware>, then horus install <package>), and wire them up under [hardware] in horus.toml. The "System Packages" column lists the host libraries that must already be present for the driver to work.

DriverHardwareSystem PackagesAvailability
SocketCANCAN bus devicescan-utils (optional)Registry package
spidevSPI devicesNone (kernel interface)Registry package
i2cdevI2C devicesi2c-tools (Pi only)Registry package
SerialSerial devicesNone (kernel interface)Registry package
GPIOGPIO pinslibraspberrypi-dev (Pi only)Registry package
PWMPWM outputslibraspberrypi-dev (Pi only)Registry package
RealSenseIntel depth cameraslibrealsense2-devRegistry package
Modbus TCPForce-torque sensorsNone (pure Rust)Planned

Installing the System Packages Drivers Need

These are the host libraries and tools from the table above. The drivers themselves come from the registry (horus install <package>).

For Raspberry Pi GPIO/I2C/SPI/PWM:

sudo apt install -y libraspberrypi-dev i2c-tools python3-smbus

# Enable hardware interfaces
sudo raspi-config
# Navigate to: Interface Options → I2C → Enable
# Navigate to: Interface Options → SPI → Enable
# Navigate to: Interface Options → Serial Port → Enable

For CAN bus debugging tools:

sudo apt install -y can-utils

# Setup virtual CAN for testing
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# Test CAN tools
cansend vcan0 123#DEADBEEF
candump vcan0

For Intel RealSense depth cameras:

# Add Intel RealSense repository
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE
sudo add-apt-repository "deb https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" -u

# Install RealSense SDK
sudo apt install -y librealsense2-dev librealsense2-utils

# Test camera
realsense-viewer

Workflow: Development to Hardware Deployment

Development (on your laptop):

# 1. Install HORUS normally (no hardware packages needed)
curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash

# 2. Create and run your project
horus new my_robot
cd my_robot
horus run   # Builds and runs your nodes. A new project declares no
            # [hardware], so nothing here touches a device.

A bare horus run is not simulation mode — it instantiates whatever real drivers [hardware] declares. Once you add hardware entries and want to develop against stubs on your laptop, use --sim:

horus install horus-sim3d   # one-time: the simulator plugin `--sim` launches
horus run --sim             # swaps every [hardware] entry marked `sim = true`
                            # for a stub node

Note: --sim auto-launches the simulator plugin (sim3d by default, or [robot].simulator). If that plugin is not installed, horus run --sim refuses to start rather than fall through to the real hardware.

Deployment (on the robot):

# 1. Install system packages on the robot
sudo apt install libraspberrypi-dev i2c-tools can-utils

# 2. Run against the real hardware
horus run --release

Hardware is declared in horus.toml, not on the command line:

[hardware]
imu = { use = "bno055", bus = "/dev/i2c-1" }

Capabilities such as GPU acceleration or the editor are command-line flags. Each capability name is forwarded to cargo as a feature of your own crate, so declare it under [rust.features] in horus.toml first — otherwise the build stops with none of the selected packages contains these features: cuda:

# horus.toml
[rust.features]
cuda = []
editor = []
horus run --enable cuda --release

Or enable them once in horus.toml (top level, above any [section] header) instead of passing the flag every time:

# horus.toml
enable = ["cuda", "editor"]

Capability names recognised by --enable (each is forwarded as a cargo feature of your crate unless noted; you must declare it under [rust.features] yourself):

  • cuda (alias gpu) → feature cuda
  • editor → feature editor
  • headless → feature headless
  • visual → feature visual
  • python (alias py) → feature python
  • opencv → feature opencv-backend
  • io-uring → feature io-uring-net
  • ultra-low-latency → feature ultra-low-latency
  • full → feature full
  • net (alias network) — not forwarded to your crate; enables the net feature on the horus dependency instead
  • sim (alias simulation) — turns on no build features by itself

Note: An unrecognised name is passed through verbatim as a cargo feature. If your crate does not declare it, the build fails with none of the selected packages contains these features: <name>.

Hardware buses are not --enable capabilities. Configure them under [hardware] in horus.toml, as shown above.

Cargo feature flags (for advanced users building with cargo directly):

  • macros - Procedural macros (node!, topics!) — enabled by default
  • net - Transparent LAN topic replication across machines (opt-in; also reachable as horus run --net)
  • telemetry - Telemetry export for live monitoring — enabled by default
  • blackbox - Flight recorder for post-mortem analysis — enabled by default

Note: Most users should use horus run --enable <capability> or set enable = ["cuda", "editor"] in horus.toml rather than passing cargo --features by hand. The four flags above are features of the horus crate itself, not of your project.

How simulation works:

Entries marked sim = true under [hardware] in horus.toml are swapped for stub nodes when you run horus run --sim; the log then shows hardware.<name>: simulation mode — using stub.

That is the whole mechanism. HORUS does not detect a missing device and silently fall back to simulation — if your driver opens /dev/spidev0.0 and it is not there, that is your driver's error to handle. Write the fallback yourself if you want one, or use --sim to develop on a laptop.

Hardware in Python

Once a driver is installed and declared under [hardware] in horus.toml, you can interact with hardware from Python nodes using HORUS Topics and standard message types:

import horus
from horus import Imu, CmdVel

def controller_tick(node):
    imu_data = node.recv("imu/data")
    if imu_data:
        node.send("cmd_vel", CmdVel(0.5, 0.0))
        # A dict works too, as long as its keys are the message's field names:
        # node.send("cmd_vel", {"linear": 0.5, "angular": 0.0})

node = horus.Node(
    name="Controller",
    subs={"imu/data": {"type": Imu}},
    pubs={"cmd_vel": {"type": CmdVel}},
    tick=controller_tick,
    rate=50
)
horus.run(node)

Declaring a topic with {"type": CmdVel} makes it a typed topic. node.send() accepts either an instance of the message class or a dict, converting a dict with CmdVel(**data).

That conversion is why the field names matter: CmdVel's fields are linear and angular, so {"linear_x": ..., "angular_z": ...} fails to convert. The failure is silent at the conversion step — the code falls through and hands the raw dict to the typed path, which rejects it:

TypeError: 'dict' object is not an instance of 'CmdVel'

The message names the type, not the bad key, so check your field names against the message when you see it.

Additional hardware drivers (CAN bus, SPI, I2C, Dynamixel servos, stepper motors, etc.) are available as registry packages. Use horus search to find drivers for your hardware.

Understanding Shared Memory

HORUS uses platform-specific shared memory for ultra-fast communication between components:

PlatformShared Memory PathNotes
Linux/dev/shm/horus_<namespace>/Native POSIX shm, fastest
macOS/tmp/horus_<namespace>/tmpfs-backed
Windows%TEMP%\horus_<namespace>\Temp directory

The namespace is default unless you set HORUS_NAMESPACE, so the directory you will normally see is /dev/shm/horus_default/ (Linux), /tmp/horus_default/ (macOS) or %TEMP%\horus_default\ (Windows).

Check available space:

# Linux
df -h /dev/shm

# macOS
df -h /tmp
# Windows
Get-PSDrive -Name $env:TEMP.Substring(0,1)

You should have at least 256MB. Most systems have 1-2GB.

If you need more space (Linux only):

# Temporarily increase to 2GB
sudo mount -o remount,size=2G /dev/shm

# Make permanent: edit /etc/fstab (requires sudo)
# Add line: tmpfs /dev/shm tmpfs defaults,size=2G 0 0

Upgrading HORUS

horus self update           # move the CLI and the cached source to the latest release
horus self update --check   # report current vs latest, change nothing

horus self update resolves the newest published release from GitHub, fetches that tag's source, downloads the platform binary for the same tag and verifies it against that release's SHA256SUMS, replaces the running binary in place, and refreshes ~/.horus/cache/horus@<version>.

Both halves move together, for the reason in Quick Install: a new CLI compiling your projects against the previous release's headers is the version skew, not a smaller version of it. On a platform with no published asset it compiles that same tag instead of downloading it.

--check prints the current and latest versions and installs nothing. It exits 0 whether or not an update is available; a non-zero exit means the check failed — GitHub was unreachable, rate-limited, or answered something unparseable. Those are errors now, not a silent "up to date".

Installing a specific version

self update only ever moves forward to the newest release. To install a particular one — including going back to an older one — re-run the installer with the tag pinned:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_VERSION=v0.4.0 bash
$env:HORUS_VERSION = 'v0.4.0'
irm https://github.com/softmata/horus/raw/main/install.ps1 | iex

Pin the same tag on every robot in a fleet and every machine is running one known tree, binary and source alike. That is what HORUS_VERSION is for, and what HORUS_INSTALL_BRANCH is not — a branch forces a source build precisely so it can never be paired with a release binary.

Telling what is installed

horus --version                       # the CLI binary
cat ~/.horus/installed_version        # what the last install recorded
cat ~/.horus/install_manifest.toml    # the full record

install_manifest.toml is written by both install.sh and horus self update:

version = "0.4.0"
tag = "v0.4.0"
commit = "…40 hex…"
topic_version = 4
source_dir = "/home/you/.horus/cache/horus@0.4.0"
binary = "/home/you/.cargo/bin/horus"
install_method = "release-binary"
installed_at = "2026-08-31T09:14:07Z"

commit is the field worth quoting in a bug report: a tag identifies a tree only until someone moves it. topic_version is the shared-memory header version, and it is the number that actually decides whether the CLI can attach to what your nodes publish — two trees can share a version string and disagree about it.

An install made before these files existed has neither. Nothing treats their absence as an error.

The version mismatch warning

If the CLI and the recorded install disagree, every horus new and every dependency-resolving horus run prints a warning naming both versions, both topic ABIs when they are known, and the command that reconciles them. It is a warning: the command carries on.

Set HORUS_STRICT_VERSION=1 to make it a hard failure instead — which is what you want in CI, and only there. The default is deliberately loud rather than fatal, because the earlier hard failure printed a remedy that could not work and left a whole release cohort unable to run horus new at all.

See Version mismatch warning for how to read one and clear it.

Docker

Two images, answering different questions. Each release tag publishes both to GitHub Container Registry:

ImageContentsSize
ghcr.io/softmata/horus:devRust, Python and CMake — builds and runs projects3.98 GB
ghcr.io/softmata/horus:latestThe CLI alone, no language toolchains284 MB

Both also carry an immutable per-release tag: ghcr.io/softmata/horus:0.4.0 and ghcr.io/softmata/horus:0.4.0-dev. Pin those on a robot — latest and dev move with every release.

# Build and run projects — this is the one you want
docker run --rm -it --shm-size=1g -v "$PWD:/work" -w /work ghcr.io/softmata/horus:dev run

To build them yourself instead — from a fork, from an unreleased commit, or on an architecture the published images do not cover:

git clone https://github.com/softmata/horus.git
cd horus

docker build --target dev -t horus:dev .
docker run --rm -it --shm-size=1g -v "$PWD:/work" -w /work horus:dev run
⚠️`:latest`, and a bare `docker build .`, give you the slim image

The default build target — and the latest tag that follows it — is the CLI-only image: about 284 MB, with no language toolchains. It can inspect a system, validate a manifest, generate message types and read logs. It cannot build or run a project: horus run there stops at

horus hint [preflight] H060
Rust toolchain not installed.

The error is correct; the image is just the wrong one. Use :dev, or --target dev when building — it carries Rust, Python and CMake, and costs 3.98 GB for them.

# CLI-only image: inspect, validate, generate
docker run --rm -v "$PWD:/work" -w /work ghcr.io/softmata/horus:latest doctor
docker run --rm -v "$PWD:/work" -w /work ghcr.io/softmata/horus:latest check

# The same, built locally
docker build -t horus:cli .
docker run --rm -v "$PWD:/work" -w /work horus:cli doctor

Shared memory. Nodes talk through /dev/shm, so a container that runs them needs a real one — Docker's 64 MB default is small for image or point-cloud topics, hence --shm-size=1g. For IPC between containers, share the namespace too:

docker run --rm --ipc=host --shm-size=1g -v "$PWD:/work" -w /work horus:dev run

Real-time scheduling needs a capability. Without it the container cannot set SCHED_FIFO and HORUS degrades — correctly and loudly, but it degrades, and a benchmark run that way measures the container rather than HORUS:

docker run --rm --cap-add=SYS_NICE --shm-size=1g -v "$PWD:/work" -w /work horus:dev run

File ownership. These images run as root, so anything horus new or horus build writes into a bind mount is root-owned on the host. Pass your own uid and a writable CARGO_HOME to keep it:

docker run --rm --user "$(id -u):$(id -g)" -e CARGO_HOME=/work/.cargo \
  -v "$PWD:/work" -w /work horus:dev build

Uninstalling

To completely remove HORUS — no clone needed:

curl -fsSL https://github.com/softmata/horus/raw/main/uninstall.sh | bash

Or ./uninstall.sh from a checkout. It prints an inventory first and asks before deleting anything, on your terminal even when the script itself arrived through the pipe. --dry-run prints that inventory and removes nothing; --yes runs unattended and keeps ~/.horus/config.toml, your credentials and the Cargo registry cache. It answers every other prompt with that prompt's default, so items 7 and 8 below — which default to yes — happen without being asked:

curl -fsSL https://github.com/softmata/horus/raw/main/uninstall.sh | bash -s -- --yes

With no terminal and no --yes — a CI job, a provisioning script — it stops and says so rather than guessing.

The uninstaller will:

  1. Remove the horus CLI binary from ~/.cargo/bin/ and ~/.local/bin/
  2. Remove ~/.horus/cache/ — the cached HORUS source and its pre-compiled deps
  3. Remove the rest of ~/.horus/ (shell env files, install state), prompting first only if it holds config.toml or credentials — answer "no" there and your config survives while cache/ still goes
  4. Remove ~/.config/horus/ (auth.json, workspace registry) on Linux, or ~/Library/Caches/horus + ~/Library/Application Support/horus on macOS, or %APPDATA%\horus + %LOCALAPPDATA%\horus on Windows
  5. Clean up shared memory files (platform-specific paths)
  6. Leave project-local .horus/ directories untouched
  7. Offer to pip uninstall horus-robotics, if that wheel is installed — the prompt defaults to yes
  8. On Linux and WSL, if /etc/security/limits.d/99-horus-realtime.conf exists, offer to remove the RT scheduling configuration — that file, /etc/sysctl.d/99-horus-realtime.conf and the horus-performance-governor systemd unit — via sudo, again defaulting to yes. That check predates horus setup-rt, which writes 99-horus-rt.conf; a limits file from a current setup-rt run does not match it and survives, so undo that separately with horus setup-rt --undo

The uninstaller does not touch ~/.cache/horus/ — the XDG package cache used by horus pkg — or ~/.local/share/horus/, which holds recordings, blackbox records and your signing keys. Blackbox recordings from a run inside a project are written to that project's own .horus/blackbox/, which item 6 leaves alone. Remove them by hand if you want them gone.

Manual uninstall (if needed):

Linux/macOS:

# Remove CLI tool
rm -f ~/.cargo/bin/horus ~/.local/bin/horus

# Remove caches, config and credentials — these are separate directories
rm -rf ~/.horus/                 # cached source + pre-compiled deps, install state, env.sh / env.fish
rm -rf ~/.cache/horus/           # XDG package cache (horus pkg) — uninstall.sh does not remove this
rm -rf ~/.config/horus/          # auth.json — your registry credentials
rm -rf ~/.local/share/horus/     # recordings, blackbox, signing keys — uninstall.sh does not remove this
# macOS: ~/Library/Caches/horus, ~/Library/Application Support/horus

# Remove source code
rm -rf ~/horus/  # or wherever you cloned it

# Clean up shared memory (optional - HORUS auto-cleans sessions)
# Linux:
rm -rf /dev/shm/horus_*
# macOS:
rm -rf /tmp/horus_*

Windows (PowerShell):

HORUS ships a native PowerShell uninstaller — prefer it over deleting by hand:

.\uninstall.ps1
.\uninstall.ps1 -DryRun    # list what would be removed, remove nothing
.\uninstall.ps1 -Yes       # unattended

Piping to iex runs the script in a child scope where -Yes never arrives, so for the unattended form fetch it into a scriptblock instead:

& ([scriptblock]::Create((irm https://github.com/softmata/horus/raw/main/uninstall.ps1))) -Yes

To do it manually instead:

# Remove CLI tool
Remove-Item -Force "$env:USERPROFILE\.cargo\bin\horus.exe"

# Remove caches, config and credentials
Remove-Item -Recurse -Force "$env:USERPROFILE\.horus"
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\horus"
Remove-Item -Recurse -Force "$env:APPDATA\horus"     # auth.json

# Remove source code
Remove-Item -Recurse -Force "$env:USERPROFILE\horus"  # or wherever you cloned it

# Clean up shared memory (optional - HORUS auto-cleans sessions)
Remove-Item -Recurse -Force "$env:TEMP\horus_*"

Troubleshooting

Having installation issues? Clear the caches and reinstall:

rm -rf ~/.horus/cache    # installer's source cache (horus@<version>) and pre-compiled deps
rm -rf ~/.cache/horus    # XDG cache root — searched first by `horus run`
curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash

If that reinstalls the same broken thing, the problem is the release, not the cache. Pin a known-good one instead of repeating the step:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_VERSION=v0.4.0 bash

or compile the same tag locally, which also side-steps a glibc floor your distribution does not meet:

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | HORUS_BUILD_FROM_SOURCE=1 bash

Common issues:

  • Missing packages: Install all required system dependencies (see above)
  • GLIBC_2.xx' not found: the release binary needs a newer glibc than this machine has — use HORUS_BUILD_FROM_SOURCE=1
  • Raspberry Pi: Ensure GPIO/I2C packages are installed
  • Jetson Nano: Ensure CUDA/JetPack packages are installed

See the Troubleshooting & Maintenance Guide for:

  • Common installation errors and detailed fixes
  • System dependency issues
  • Platform-specific problems
  • 15+ solved issues with step-by-step solutions

Next Steps

With HORUS installed, proceed to one of the following:

  1. Quick Start Tutorial - Build your first HORUS application
  2. CLI Reference - Complete command documentation
  3. Examples - Sample HORUS applications

Recommended: Start with the Quick Start Tutorial.