The Nazara Logo

Welcome to Nazara's documentation hub!

Nazara is a Rust tool used to automate the registration and update process of machines and VMs in the IPAM tool NetBox.

This documentation is split by target group. Check the navigation bar on the left hand side for all pieces of information that are relevant for you.

Found a bug?

Compatibility

We strive to make sure to stay compatible with the most recent NetBox version. Here you can see which version of Nazara is compatible with which version of NetBox (and if we still support it). When major ports to new versions of NetBox happen - which usually includes breaking changes - the old version of Nazara will be moved to its own branch and tagged accordingly.

Nazara VersionNetBox VersionBranchmaintained?
v0.2.0v4.3.x, and newermain
v0.1.1v4.3.x, v4.4.x, 4.5.x*version/0.1.1
v0.1.0v4.3.x, v4.4.xversion/0.1.0
v0.1.0_beta.3v4.3.x, v4.4.xversion/beta-3
v0.1.0_beta.2v4.3.x, v4.4.xversion/beta-2
v0.1.0_beta.1v4.3.xversion/beta-1
v0.1.0_alpha.2v3.6.xversion/alpha-2

Maintenance work on these older versions is not planned currently.

Warning

Nazara was developed for and on Linux systems. We have no plans to support Windows or Mac in the foreseeable future. If you would like to add support for this, please open a discussion in our GitHub repository.

Distribution Channels

This is an overview of channels through which you can get Nazara.

Platform/ChannelVersionOfficial?
crates.iov0.1.0yes
openSUSE Tumbleweed--coming soon----coming soon--

For information about who maintains a package you are looking for, try looking into the PACKAGERS.md file in our project's root directory.

Design Documentation

Here you can find all documentation relating to Nazara's structure, architecture and style.

Nazara Architecture Guide

Introduction

This document explains the structure and architecture chosen for Nazara and gives an overview over its components.

Nazara is a tool for automating the registration and update process of physical machines and virtual machines in NetBox, a open-source IPAM tool.

Ease of use is important in this regard, users should be able to pick Nazara up quickly, and be able to comfortably integrate it into their daily workflow. With automation if they wish.

Nazara, very roughly, has 3 main functions: Collection, Translation and Publishing. (Excluding the configuration module here)

The Collection module is soley responsible for collecting information about the host system the user wants to register. This includes things like the device's network interfaces, IP addresses and other hardware information. All collected from the DMI tables.

The Translator module handles data process and translates all information that the collector has collected into appropriate API payload objects. These objects are defined by the NetBox API and brought to Nazara via the thanix_client crate. This crate is generated automatically by us and serves as the API client library used for making API requests to NetBox.

The Publisher module is the most "superior" module hierarchially. It handles the whole registration process and decided when which translation needs to be made and which API call to be sent.

Goals

Now, what problem does Nazara solve?

Some system administrators have a overwhelming number of machines. Or maybe they get sent upgrades to prototype hardware multiple times a year. When dealing with this large amount of machine turnover, Nazara provides an easy to use tool to eliminate the tedious task of manually registering or updating device or VM information.

What are the essential features and goals for Nazara?

  • Ease of use
  • Registration of machines and VMs and their associated components
  • Automatization of custom field collection

Non-Goals

Nazara is simply a tool to register/update machines and VMs in NetBox. Beyond that it is currently not planned to have any other kind of managing role alongside NetBox or other IPAM tools.

Design

Nazara - codewise - is supposed to be highly modular and respect strict separation of concerns. This means that one logical unit does only one thing and contain all the logic necessary to achieve their goal.

This starts with modules: The Collector, Configuration, Translator and Publisher modules all only have one responsibility.
We believe that - wherever sensible and possible - each module, each file and each function should have one responsibility. For a new additional feature we would rather introduce another module that handles that responsibility than break this principle.

In addition to this all interfaces between modules should be as small as possible. Preferably one or two functions. This makes it easier to mentally follow the execution flow and avoids any kind of cross referencing or massive rewrites once a single module needs to be rewritten.

This philosophy should also extend to Nazara's Git-History. You can find more on that in the Contributor Guide.

High-Level-Design

As mentioned before, Nazara is designed with strict separation of concerns in mind. The following is a very high-level overview of Nazara's four logical parts:

  • The Collectors
  • The Configuration Package
  • The Publisher
  • The Main module

It also shows the two modules of our thanix_client crate, which we created specifically for sending API requests to NetBox. More on that later.

The following diagram gives an overview over the program flow once started by a user. Note that the exact function calls within translator.rs and the difference between POST/PATCH requests has been omitted for simplicity.

In reality, the publisher checks if the device has already been registered. The easiest way to do this would be to pass the ID the device or VM has in NetBox, however we are capable of searching given several parameters like serial number or UUID. Though these methods are less reliable.

In any way, the publisher then decides whether a POST or PATCH request shall be made.

Nazara Code Style Guide

General Guide

In regards to formatting, we follow the current Rust code style set by the Cargo formatter (cargo fmt).

The easiest way to enforce this, is using pre-commit to automatically format and check any code before it is committed.

Info

This style is enforced by our CI as well. PRs will not be accepted unless the formatting issues are fixed.

Panics

We heavily discourage the use of the panic!() macro when aborting the process. Instead, we prefer to handle an error and abort gracefully with a helpful error message and error code.

We only want to panic, when the issue we encounter is both catastrophic and unfixable by the user.

Example

These are scenarios in which we don't want to panic:

  • When input or config parameters are missing
  • When connection to NetBox fails
  • When an API request returns a different error code that Ok

While we want to panic in these cases:

  • When filesystem operations fail
  • When Nazara is not run as root

Instead of panicking, we write custom error types to wrap errors of certain functions and include a well formatted error message alongside the error.

For detailed information on Nazara's error handling approach, including the NazaraError enum and best practices, see the Error Handling Guide.

````admonish example collapsible=true title="Example:src/error.rs" In this example, you can get an overview over the error.rs module and get an idea on how to handle errors in the code.

#![allow(unused)]
fn main() {
#[derive(Debug)]
pub enum NazaraError {
    /// Something went wrong trying to parse DMI tables.
    Dmi(dmidecode::InvalidEntryPointError),
    /// Used to indicate that the collection of system data failed.
    UnableToCollectData(String),
    /// Used to indicate that one of the collected NWIs might be malformed or invalid.
    InvalidNetworkInterface(String),
    /// Used in case the NWI collector crate cannot find any interfaces.
    NoNetworkInterfaces(String),
    /// Rust couldn't convert a byte sequence to a UTF-8 string.
    UnableToParseUTF8(std::string::FromUtf8Error),
    InvalidPluginOutput(serde_json::Error),
    PluginExecution(String),
    /// Used for handling errors during file operations.
    FileOpError(std::io::Error),
    /// Indicates that no config file has been found, or it has been moved or deleted during program startup.
    NoConfigFileError(String),
    /// Indicates that a required config option is missing from the config file.
    MissingConfigOptionError(String),
    /// The Deserialization of a buffer to a type failed.
    DeserializationError(toml::de::Error),
    /// An error occured during the serialization of config parameters to a TOML value.
    SerializationError(toml::ser::Error),
    /// An error has occured while accessing data returned by NetBox.
    NetBoxApiError(String),
    /// Data returned by NetBox is incomplete.
    NetBoxMissingField(String, String),
    /// Wraps a `reqwest::Error`. Used for handling failures with requests and responses.
    Reqwest(reqwest::Error),
    /// NetBox returned a response with an unexpected code.
    UnexpectedResponse(reqwest::blocking::Response),
    /// Used to indicate the `thanix_client` version is incompatible with NetBox.
    VersionMismatch,
    /// Used to indicate that NetBox's initial response does not contain the application version.
    MissingVersion,
    /// Wraps a `serde_json::Error`. Used to handle failures with response serialization.
    JsonParse(serde_json::Error),
    NetlinkError(String),
    /// Specified primary IP has not been registered with this device or VM.
    InvalidPrimaryIp(String),
    /// Expects a `String` message. Used for edge cases and general purpose error cases.
    Other(String),
}

pub type NazaraResult<T> = Result<T, NazaraError>;

impl NazaraError {
    /// Log this error with failure! and return it wrapped in Err(...)
    pub fn fail<T>(self) -> NazaraResult<T> {
        failure!("{}", self);
        Err(self)
    }

    /// Log this error with additional context prefix.
    /// Used when we need to log multiple errors but continue processing to fail in the end
    /// either with the calling function or some other higher instance.
    ///
    /// The optional context in this case refers to the module or program part that the error
    /// occurred in. For example: [DHCP-Mode].
    pub fn log(&self, context: Option<&str>) {
        if let Some(ctx) = context {
            failure!("[{}] {}", ctx, self);
            return;
        }
        failure!("{}", self);
    }
}

impl std::fmt::Display for NazaraError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NazaraError::Dmi(err) => {
                write!(f, "DMI Error: {err}")
            }
            NazaraError::UnableToCollectData(err) => {
                write!(f, "Collector Error: {err}")
            }
            NazaraError::InvalidNetworkInterface(err) => {
                write!(f, "Network Collector Error: {err}")
            }
            NazaraError::NoNetworkInterfaces(err) => {
                write!(f, "Network Collector Error: {err}")
            }
            NazaraError::UnableToParseUTF8(err) => {
                write!(f, "Unable to parse stdout from UTF8 to string: {err}")
            }
            NazaraError::InvalidPluginOutput(err) => {
                write!(f, "Plugin returned invalid JSON: {err}")
            }
            NazaraError::PluginExecution(err) => {
                write!(f, "Plugin execution failed: {err}")
            }
            NazaraError::FileOpError(err) => {
                write!(f, "File operation failed: {err}")
            }
            NazaraError::NoConfigFileError(err) => {
                write!(f, "No config file found: {err}")
            }
            NazaraError::MissingConfigOptionError(err) => {
                write!(f, "Missing required config parameter: {err}")
            }
            NazaraError::DeserializationError(err) => {
                write!(f, "Invalid config file: {err}")
            }
            NazaraError::SerializationError(err) => {
                write!(f, "Serialization error: {err}")
            }
            NazaraError::Reqwest(err) => {
                write!(f, "Failed to perform an HTTP request: {err}")
            }
            NazaraError::NetBoxApiError(err) => {
                write!(f, "NetBox API Error: {err}")
            }
            NazaraError::NetBoxMissingField(struc, field) => {
                write!(
                    f,
                    "NetBox returned incomplete data: Structure \"{struc}\" is missing field expected field \"{field}\""
                )
            }
            NazaraError::UnexpectedResponse(err) => {
                let status = err.status();
                write!(
                    f,
                    "Got an unexpected HTTP response {} from NetBox: {:?}",
                    status, err
                )
            }
            NazaraError::NetlinkError(err) => {
                write!(f, "Netlink Error: {err}")
            }
            NazaraError::VersionMismatch => {
                write!(
                    f,
                    "Client version incompatible with NetBox version! Use client v1.x for NetBox v3.6.x and above, and v2.x for NetBox 4.x.",
                )
            }
            NazaraError::MissingVersion => {
                write!(
                    f,
                    "NetBox version missing from response. Please check your installation.",
                )
            }
            NazaraError::JsonParse(error) => {
                write!(f, "Error while parsing JSON: {error}")
            }
            NazaraError::InvalidPrimaryIp(addr) => {
                write!(
                    f,
                    "Configured primary IP '{addr}' does not belong to any interface registered to this device."
                )
            }
            NazaraError::Other(msg) => f.write_str(&msg),
        }
    }
}

impl From<std::io::Error> for NazaraError {
    fn from(value: std::io::Error) -> Self {
        Self::FileOpError(value)
    }
}

impl From<serde_json::Error> for NazaraError {
    fn from(value: serde_json::Error) -> Self {
        Self::JsonParse(value)
    }
}

impl From<std::string::FromUtf8Error> for NazaraError {
    fn from(value: std::string::FromUtf8Error) -> Self {
        Self::UnableToParseUTF8(value)
    }
}

impl From<reqwest::Error> for NazaraError {
    fn from(value: reqwest::Error) -> Self {
        Self::Reqwest(value)
    }
}

impl From<dmidecode::InvalidEntryPointError> for NazaraError {
    fn from(value: dmidecode::InvalidEntryPointError) -> Self {
        Self::Dmi(value)
    }
}

impl From<&str> for NazaraError {
    fn from(value: &str) -> Self {
        Self::Other(value.to_owned())
    }
}

impl From<toml::de::Error> for NazaraError {
    fn from(value: toml::de::Error) -> Self {
        Self::DeserializationError(value)
    }
}
}

## Focus on Readability

When writing code, we value readability above complexity.

Rust offers a lot of possibilities when it comes to reducing code down into a
one-line statement. And that's nice, and sometimes even necessary.

However, we want to take a **readability first** approach with our codebase.
Both to aid maintainability and increase accessibility for newcomers.

Of course, sometimes using Rust's more "advanced" features will drastically
improve performance. So a balance has to be struck between readability and
performance. It is very hard to define a solid policy for this, so this is a
decision every developer and contributor has to do for themselves and - when
necessary - engage in discussion with maintainers, devs and other contributors
about their approach and possible alternatives.

## Documenting Code

For a similar reason, we encourage devs to properly document their
contributions. This includes but is not limited to:

- Using inline comments to explain possibly hard to understand syntax
- Using Rust's powerful docstring feature to properly document functions,
  structs and modules
- Adding to this documentation when applicable
- Filling out Issue and PR templates appropriately to aid maintainers review
  their changes

The following examples will show you an ideal docstring style.

````admonish example title="Example: Documenting Functions" collapsible=true
**Documenting Functions:**
```rust
/// This function does X.
///
/// This function does X by doing Y using Z. (Detailed explanation optional)
///
/// # Parameters
/// * `arg: str` - A string argument to process
///
/// # Returns
/// * `Ok(str)` - Returns A, if ...
/// * `Err` - Returns an `ErrType`
pub fn foo(arg: str) -> Result<str, Err> {
 // ...
}
```
While not universally used by all projects, we use `# Parameters` and `# Returns`
sections, especially for larger functions.

If a function does not take arguments, the `# Parameters` section can be omitted.
However, if the function does not return (`!` type) - or returns `()`, this has to be indicated
in the `# Returns` section.

Other sections we use are:

* `# Aborts` - If the function aborts the program. (E.g When input parameters are missing)
* `# Panics` - If the function can cause a `panic!()`.

For both of these sections, list all - or at least the most common - reasons this behaviour can
occur. This can help debugging immensely.

```rust
/// This function does X.
///
/// # Parameters
/// - `path: &str` - The path to a file
///
/// # Returns
/// - `String` - The contents of the file.
///
/// # Aborts
/// This function will exit the process if the file cannot be found.
pub fn read_file(path: &str) -> String {
    match fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(err) => {
            eprintln!("[Error] File '{}' does not exist: {}", path, err);
            process::exit(1);
        }
    }
}
```

Example: Documenting Structs

Documenting Structs:

#![allow(unused)]
fn main() {
/// Information about a Person.
pub struct Person {
    /// The name of the Person.
    pub name: String,
    /// The age of the Person.
    pub age: i64,
}
}

It is encouraged to briefly document every field of your struct, whether they are pub or not does not matter.

Example: Documenting Modules

Documenting Modules:

#![allow(unused)]
fn main() {
//! This module handles X.
}

You can go into depth here about what a module does. This is encouraged for larger modules.

Hint

Please be aware, that a one-liner docstring above a function will not suffice. Maintainers may ask you to stick to the given format for your contribution.

We are aware that to some of you, this feels like cluttering the code files. However, we believe that properly documenting our code is the key to providing a more inclusive and more maintainable development experience for all.

Terminal Output/User Interface

When it is necessary to inform the user about a process, we want to make it short, but as expressive as possible. For this, the following styles apply:

  • Process X has been started... - Basic white text indicates the current process.
  • \x1b[32m[success]\x1b[0m Something has succeeded - Green colored [success] prefix before the message.
  • \x1b[31m[error]\x1b[0m Something has failed! - Red colored [error] prefix before error message. This should automatically be added by our custom error types when they are given a error message.
  • \x1b[36m[info]\x1b[0m Information level message. - Light blue colored [info] prefix.
  • \x1b[33m[warning]\x1b[0m Something went wrong, but we can continue... - Yellow colored [warning] prefix.

To unify this coloring we have implemented several macros to be used for these status messages. These apply formatting and colors automatically and disable colors when the host's terminal does not support it.

The macros are called success!, warn!, failure!, info!. For information on when to use these macros and how they integrate with NazaraError, see the Error Handling Guide.

Example: Status Message Macros

#![allow(unused)]
fn main() {
match some_func(x) => {
    Ok(_) => {
        success!("This worked!");
    },
    Err(e) => {
        failure!("An error occurred: {}", e);
        // Handle the error.
    }
}
}

Nazara Error Handling Guide

Philosophy

Nazara follows a "fail gracefully" philosophy. We prefer returning descriptive errors over panicking, reserving panics only for truly catastrophic and unfixable situations.

Core Principles:

  • Single Source of Truth: Error messages are defined once in NazaraError variants and displayed consistently
  • Immediate Feedback: Errors are logged immediately using the failure! macro for user visibility
  • Clean Propagation: Most functions use the ? operator without logging noise
  • Context When Needed: Optional context prefixes indicate which module or operation triggered the error

The NazaraError Enum

All errors in Nazara are represented by the NazaraError enum in src/error.rs:

#![allow(unused)]
fn main() {
pub enum NazaraError {
    /// Something went wrong trying to parse DMI tables.
    Dmi(dmidecode::InvalidEntryPointError),
    /// Used to indicate that the collection of system data failed.
    UnableToCollectData(String),
    /// Used for handling errors during file operations.
    FileOpError(std::io::Error),
    /// Indicates that a required config option is missing from the config file.
    MissingConfigOptionError(String),
    /// An error occurred while accessing data returned by NetBox.
    NetBoxApiError(String),
    /// Expects a `String` message. Used for edge cases and general purpose error cases.
    Other(String),
    // ... other variants
}
}

Display Trait Implementation

Every NazaraError variant implements std::fmt::Display to provide user-friendly error messages:

#![allow(unused)]
fn main() {
impl std::fmt::Display for NazaraError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NazaraError::FileOpError(err) => {
                write!(f, "File operation failed: {err}")
            }
            NazaraError::NetBoxApiError(msg) => {
                write!(f, "NetBox API Error: {msg}")
            }
            NazaraError::Other(msg) => f.write_str(&msg),
            // ... other variants
        }
    }
}
}

Important

The Display implementation should never call failure! or any other logging macro. It should only format the error message.

Convenience Methods

The NazaraError enum provides two convenience methods for common error handling patterns:

fail() - Log and Return

#![allow(unused)]
fn main() {
impl NazaraError {
    /// Log this error with failure! and return it wrapped in Err(...)
    pub fn fail<T>(self) -> NazaraResult<T> {
        failure!("{}", self);
        Err(self)
    }
}
}

Purpose: Combines logging and returning into a single operation.

Example: Using the fail-function to log and return an error

#![allow(unused)]
fn main() {
return NazaraError::Other("Config file contains invalid entries".to_owned()).fail();
}

log() - Log with Context

#![allow(unused)]
fn main() {
impl NazaraError {
    /// Log this error with additional context prefix.
    /// Used when we need to log multiple errors but continue processing to fail in the end
    /// either with the calling function or some other higher instance.
    ///
    /// The optional context in this case refers to the module or program part that the error
    /// occurred in. For example: [DHCP-Mode].
    pub fn log(&self, context: Option<&str>) {
        if let Some(ctx) = context {
            failure!("[{}] {}", ctx, self);
            return;
        }
        failure!("{}", self);
    }
}
}

Purpose: Log errors with optional context when you need to accumulate multiple errors before failing.

Example: Logging an error but continue processing

#![allow(unused)]
fn main() {
// Accumulate errors, fail at the end
for validation in validations {
    if !validation.is_valid() {
        let err = NazaraError::Other(format!("Validation '{}' failed", validation.name()));
        err.log(None);
        error_count += 1;
    }
}

if error_count > 0 {
    return NazaraError::Other("Multiple validations failed".to_owned()).fail();
}
}

Example: Logging an error with context

context indicates where or why this error has occurred. For example when working with the ip-mode flags, errors rooted in the changes of IP addresses by DHCP or something similar have the context "DHCP-Mode".

#![allow(unused)]
fn main() {
let err = NazaraError::NetBoxApiError("IPv4 not found".to_owned());
err.log(Some("DHCP-Mode"));
return Err(err);
}

Examples

Example: Logging with Return

Use when an error is terminal and should be returned immediately.

#![allow(unused)]
fn main() {
return NazaraError::Other("Config file contains invalid entries".to_owned()).fail();
}

Example: Log with Context, Then Return

Use when the error needs additional module/operation context.

#![allow(unused)]
fn main() {
let err = NazaraError::NetBoxApiError(
    format!("IPv4 address \"{}\" was not registered in NetBox", ip)
);
err.log(Some("DHCP-Mode"));
return Err(err);
}

Or in a closure (for use with ok_or_else):

#![allow(unused)]
fn main() {
let ipv4_id = search_ip(client, &ip.to_string(), None)?.ok_or_else(|| {
    let err = NazaraError::NetBoxApiError(format!("IPv4 \"{}\" not found", ip));
    err.log(Some("DHCP-Mode"));
    err
})?;
}

Example: Accumulate Errors, Fail Later

Use when processing multiple items and collecting all errors before failing.

#![allow(unused)]
fn main() {
let mut config_errors = Vec::new();

for field in required_fields {
    if config.get(field).is_none() {
        let err = NazaraError::MissingConfigOptionError(field.to_string());
        err.log(None);
        config_errors.push(err);
    }
}

if !config_errors.is_empty() {
    return NazaraError::Other("Missing required config fields".to_owned()).fail();
}
}

Example: Custom User-Facing Messages

When you need to provide specific user guidance, use failure! directly.

#![allow(unused)]
fn main() {
failure!(
    "Tag '{}' does not exist. Use --prepare-environment to create it.",
    tag_name
);
}

This is clearer than trying to fit the guidance into a NazaraError variant's display message.

When to Use What

Use .fail() when:

  • The error is terminal (nothing else can be done)
  • The error message is sufficient (no additional context needed)
  • You want clean, single-line error handling
  • This is the 90% common case

Use .log() when:

  • You need to accumulate multiple errors before failing
  • You want to add module/operation context
  • The error should be logged but processing should continue
  • You need to track multiple issues

Use failure! directly when:

  • You need a custom message with user guidance
  • The message is temporary/debugging (will be replaced later)
  • You're providing feedback before returning a different error

Don't use either when:

  • You can simply return the error with ? (no logging needed)
  • The caller will handle the error (low-level utilities)
  • The error will be logged by the caller anyway

Best Practices

1. Choose the Right Error Variant

Prefer specific variants over Other when possible:

#![allow(unused)]
fn main() {
// GOOD
return NazaraError::MissingConfigOptionError("netbox_uri".to_owned()).fail();

// OK (when no specific variant fits)
return NazaraError::Other("Custom error message".to_owned()).fail();
}

2. Include Context in Error Messages

When using Other, make the message descriptive:

#![allow(unused)]
fn main() {
// GOOD
return NazaraError::Other("Config file contains invalid entries".to_owned()).fail();

// BAD
return NazaraError::Other("Error".to_owned()).fail();
}

3. Use Context for Module Identification

When logging with context, use module or operation names:

#![allow(unused)]
fn main() {
err.log(Some("DHCP-Mode"));
err.log(Some("Config-Parser"));
}

4. Don't Duplicate Error Messages

The error message should be defined once—in the Display implementation or the error construction:

#![allow(unused)]
fn main() {
// GOOD
let msg = "Config file contains invalid entries".to_owned();
failure!("{}", msg);
return Err(NazaraError::Other(msg));

// REDUNDANT (duplicates the message)
failure!("Config file contains invalid entries");
return NazaraError::Other("Config file contains invalid entries".to_owned()).fail();
}

5. Let Errors Propagate

For low-level utility functions, just return errors. Let the caller decide whether to log:

#![allow(unused)]
fn main() {
// In a low-level utility
pub fn read_config_file() -> NazaraResult<ConfigData> {
    let mut file = File::open(get_config_path(true))?;  // Just propagate
    // ...
}

// In the calling function
let config = read_config_file()
    .map_err(|e| e.log(None))?;  // Log here
}

Error Flow

[Deep Function] → Creates/Returns NazaraError
        ↓ (via ? operator)
[Middle Function] → Propagates with ?
        ↓ (via ? operator)
[Application Logic] → Logs with .fail() or .log()
        ↓
[main.rs] → Catches error, logs with failure!
        ↓
[stderr] → User sees formatted error message

Example: Complete Error Handling Flow

Here's how error handling works in a real scenario:

// 1. Low-level function (no logging, just propagation)
pub fn read_config_file() -> NazaraResult<ConfigData> {
    let mut file = File::open(get_config_path(true))?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    toml::from_str(&contents).map_err(NazaraError::DeserializationError)
}

// 2. Mid-level function (propagates errors)
pub fn validate_config(config: &ConfigData) -> NazaraResult<()> {
    if config.netbox_uri.is_empty() {
        return Err(NazaraError::MissingConfigOptionError("netbox_uri".to_owned()));
    }
    Ok(())
}

// 3. Application logic (logs and returns)
pub fn setup_configuration() -> NazaraResult<ConfigData> {
    let config = read_config_file()?;
    validate_config(&config)?;
    
    if config.is_invalid() {
        return NazaraError::Other("Config validation failed".to_owned()).fail();
    }
    
    Ok(config)
}

// 4. Main entry point (logs final error)
fn main() {
    match nazara::run() {
        Ok(_) => success!("All done!"),
        Err(e) => failure!("{}", e),  // Uses failure! for consistency
    }
}

Remember: Errors should be logged once, at the appropriate level, with sufficient context.

Contributing to Nazara

Thank you for considering contributing to Nazara!

The following documents should provide you with all the information you need to start contributing to Nazara.

If you are completely new, make sure you have the following things installed:

  • rustc: The Rust compiler
  • rustup: The Rust toolchain manager
  • cargo: The Rust package manager

If you are unsure about how to set up a Rust development environment, please refer to the Rust documentation for a guide.

Becoming a Packager

If you would like to package Nazara for your distribution, please open an Issue and refer to our Becoming a Packager guide for more info.

Setting up a Test Environment

In case you don't have a dedicated test instance of NetBox, you can easily set up a local instance via docker-compose.

  1. Simply clone the netbox-docker repository
  2. Modify the docker-compose.yml file to the required NetBox version

Note

Depending on the version of Nazara you are working with, you may need to adjust the image version number in your docker-compose.yaml to fit your needs. In case you are working on a specific issue, please make sure the Nazara version is compatible with the NetBox version you are using and also make sure that we still support that version.

services:
    netbox: &netbox
        image: docker.io/netboxcommunity/netbox:v4.3.3
        ...

and execute these commands in accordance to netbox-docker's setup guide:

git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee docker-compose.override.yml <<EOF
services:
  netbox:
    ports:
      - 8000:8080
EOF
  1. Then build the environment by running docker compose up
  2. When the container is built, you need to create a superuser test account
docker compose exec netbox /opt/netbox/netbox/manage.py createsuperuser

Simply select a username and password of your wishes.

  1. When that is done, you need to create an API Token username > API Tokens > Add a Token and paste it, along with the container's URL into the Nazara config file at ~/.nazara/config.toml

  2. After that, you need to create a few dummy fields that are sadly required to create a device via API

    • Device Type
    • Device Role
    • Manufacturer
    • Site (And depending on what you want to work on, replicate the custom fields you need 1:1 from your production instance.)

    If you want to specify and play around with some optional fields, you must create the objects you reference (like e.g Tenants) first.

  3. After that's done, take the IDs of these objects and place it into the corresponding fields in the ~/.nazara/config.toml

Important

Currently, the generation of the config file is still a bit wonky, so if it is not generated upon first executing nazara, copy and paste the template from the README or src/configuration/config_template.toml.

Now it should work, if you have trouble setting it up, please reach out in the discussion section.

Testing VMs

In the previous chapter, you read up on how to set up a local NetBox instance using netbox-docker.

This guide will show you how to set up a VM that you can register/update on this container using Nazara.

Prerequisites

  • Host machine with the netbox-docker deployment running locally
  • Virtualization software (QEMU/KVM)
  • A Linux ISO of your choosing

Create a new Linux VM

Use a GUI to create/manager VMs

For creating and managing VMs, we recommend you use a tool like virt-manager.

You can find a step-by-step guide on how to create a new VM here

Using the Terminal

Info

These examples are specifically for openSUSE Tumbleweed.

For package names on other distros, please refer to the list below or check your distro's package lists.

1. Install required packages

sudo zypper install -y qemu-kvm libvirt virt-manager virt-install bridge-utils
sudo systemctl enable --now libvirtd

2. Create Linux VM

Replace $ISO_PATH with the path to the ISO file and $DISK_PATH to the path where you want your virtual disk to be located.

  1. Create a new virtual disk image
qemu-img create -f qcow2 $DISK_PATH 20G
  1. Create the Virtual Machine
sudo virt-install \
    --name nazara-test-vm \
    --ram 2048 \
    --vcpus 2 \
    --os-type linux \
    --os-variant $VARIANT \
    --network network=default \
    --graphics spice \
    --cdrom $ISO_PATH \
    --disk path=$DISK_PATH,format=qcow2

Example: Creating Ubuntu VM

This is an example on how to create a new VM using Ubuntu Desktop 25.10.

  1. Create the virtual disk
qemu-img create -f qcow2 /var/lib/libvirt/images/ubuntu.qcow2 20G
  1. Create the VM
sudo virt-install \
  --name nazara-test-vm-ubuntu \
  --ram 2048 \
  --vcpus 2 \
  --os-type linux \
  --os-variant ubuntu25.10 \
  --network network=default \
  --graphics spice \
  --cdrom /home/user/Downloads/ubuntu-25-10-desktop-amd64.iso \
  --disk path=/var/lib/libvirt/images/ubuntu.qcow2,format=qcow2

Example: Creating openSUSE Tumbleweed VM

This is an example on how to create a new VM with openSUSE Tumbleweed.

qemu-img create -f qcow2 /var/lib/libvirt/images/tumbleweed.qcow2 20G
  1. Create the VM
sudo virt-install \
  --name nazara-test-vm-tumbleweed \
  --ram 2048 \
  --vcpus 2 \
  --os-type linux \
  --os-variant opensuse-tumbleweed \
  --network network=default \
  --graphics spice \
  --cdrom /home/user/Downloads/ubuntu-25-10-desktop-amd64.iso \
  --disk path=/var/lib/libvirt/images/ubuntu.qcow2,format=qcow2

Network Notes:

  • Default NAT (virbr0) -> host reachable from VM at 192.168.122.1 (on openSUSE)
  • Optional: Use bridge or host-only network for static IP

Transfer Nazara to the VM

If you used the standard network option for your VM, it is most likely not able to connect to the internet directly. This means you cannot pull Nazara's GitHub repo.

You can however, move your binary and config file from your host machine to your VM using SSH.

First check if you can reach the NetBox instance from your VM. Then simply run this bash script to copy everything else over:

#!/bin/bash

# Usage: ./copy_to_vm.sh <VM_IP> <VM_USER> <PATH_TO_NAZARA_PROJECT>
# Example:
# ./copy_to_vm.sh 192.168.122.123 alice /home/user/Nazara

set -e

VM_IP="$1"
VM_USER="$2"
PROJECT_PATH="$3"

if [[ -z "$VM_IP" || -z "$VM_USER" || -z "$PROJECT_PATH" ]]; then
    echo "Usage: $0 <VM_IP> <VM_USER> <PATH_TO_NAZARA_PROJECT>"
    exit 1
fi

NAZARA_BINARY="$PROJECT_PATH/target/release/nazara"
CONFIG_FILE="/root/.config/nazara/config.toml"

if [[ ! -f "$NAZARA_BINARY" ]]; then
    echo "Error: Nazara binary not found at $NAZARA_BINARY"
    exit 1
fi

if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "Error: config.toml not found at $CONFIG_FILE"
    exit 1
fi

echo "Copying Nazara binary to VM home directory..."
scp "$NAZARA_BINARY" "$VM_USER@$VM_IP:/home/$VM_USER/"

echo "Copying config file to VM root config directory..."
ssh "$VM_USER@$VM_IP" "sudo mkdir -p /root/.config/nazara"
scp "$CONFIG_FILE" "$VM_USER@$VM_IP:/tmp/config.toml"
ssh "$VM_USER@$VM_IP" "sudo mv /tmp/config.toml /root/.config/nazara/config.toml"

echo "Done!"

Contributing Workflow

Setup

  1. Fork the project repository by clicking on the "Fork" button on the project's GitHub page. This will create a copy of the repository under your GitHub account.
  2. Clone your forked repository to your local machine using the git clone command. This will create a local copy of the repository that you can work on.
  3. Install the project dependencies by installing libopenssl-dev and libdbus-sys are installed on your system. Both are required by Nazara to compile.

Note

The names of both of these libraries can vary depending on your distribution. The examples are for openSUSE Tumbleweed.

  1. Install and set up pre-commit for code quality checks. This tool will automatically execute the hooks we implemented which will check your code for formatting or styling issue before each commit.

Note

Note: If pre-commit fails on execution, be sure to run cargo format and cargo clippy on your code and fix any issues raised by these tools.

Changing Documentation

In case you want - or need - to do changes to this documentation, you need to install both mdbook and mdbook-admonish via cargo.

Simply run these commands:

cargo install mdbook mdbook-admonish

To build the documentation run these commands from the repo's root:

mdbook build docs && mdbook serve docs

This will create a local deployment of the documentation at localhost:3000 with automatic rebuilds upon change.

Making changes

Once you have set up the project your can start working on your contribution.

1. Create a new branch for your changes

Note that working branches should have one of these prefixes.

  • feature/: For adding new features to Nazara
  • docs/: For changing documentation
  • ci/: For CI/CD maintenance
  • fix/: For bugfixes
  • dep/: For deprecations
  • tests/: For branches that add/change tests

Example

Examples of good or bad branch names would look like this:

feature/add-vm-creation, rather than add-vm

2. Make meaningful commits

It is important to pay attention to the scope of your contribution. To this end please only make changes in one Pull Request which are related to your specific contribution.

This makes it easier for us to review your PR and to keep the commit history clean. (If you encounter something else you want to change, which is not directly linked to your contribution, please open a PR on a separate branch for this change.)

Hint

Please refer to our Code Style Guide to find out how our code should be formatted and documented.

3. Include tests in your code

Automated tests are essential to ensure the correctness and reliability of the codebase. Therefore it is required that Pull Requests, which change the existing behaviour of the codebase (e.g by adding features), must be covered with tests by the contributor whenever possible in the same PR as the contribution itself. Code without tests might be rejected or take longer to process.

4. Push your branch to your fork.

5. Open a PR against the main repository.

Fill out the PR form and provide a detailed description of what your PR does and the reason or motivation behind the change.

Hint

To make it easier for us to process your contribution, please stick to the PR template.

6. Wait for CI to pass.

Our CI workflows run on pushes and PRs and will check for code quality, format and vulnerabilities. They might also execute all tests they find. It is imperative that all checks are green before a contribution is green. Please check and fix any errors the workflows find.

7. Wait for review.

That's it, now you can, if not already automatically done so, request a review by one of the repository maintainers. We will come back to you as quickly as we can.

Pay Attention To

  1. To ensure that all code is properly formatted, please run cargo format on you code before submitting it. pre-commit will tell you when your code is not properly formatted.
  2. Documentation is key. We require, that all code contributions are properly documented not only with commit messages and meaningful PR descriptions, but also that the code itself is properly documented with docstrings. This ensures that new contributors and maintainers alike can navigate their way through the codebase. This has the added benefit that your PR can be accepted much quicker too.

Important

For any other questions regarding style, please refer to the Code Style Guide.

Introducing a Dependency

While we would prefer contributors not to introduce new dependencies, we acknowledge that this is not always possible.

Therefore, please refer to our Dependency Policy to see which dependencies we accept, and also please be ready to explain why introducing this dependency was necessary.

A word on vulnerabilities

If you discover a security vulnerability in our code, please inform us privately immediately according to our Security Policy.

If you wish to fix a vulnerability, please also inform us and stand by for our green light. We would still like to investigate the vulnerability for ourselves to get an overview over the severity and scope of the problem. Please refrain from publishing a fix until we came back to you or have published a security advisory.

License Disclaimer

By submitting a contribution to The Nazara Project, you agree that your contribution shall be licensed under the same license(s) as the project at the time of contribution.

You also grant the project maintainers the right to relicense the project, including your contribution, under any future version of those license(s), or under any other license that is free and open source software (FOSS) and compatible with the current license(s).

Documenting Nazara

Hint

This information is pretty much applicable to all repositories of the Nazara project.

This document will give you an overview about our documentation process. What needs to be documented, where does it go and who is responsible for keeping documentation clean is all part of this guide.

What needs to be documented?

Anything that has implication for people outside the immediate contributor team needs clear documentation. This includes function behaviour users rely on, steps packagers follow to produce artifacts, and processes maintainers use to operate or fix the project.

Specifically, document:

  • User-facing Features: Installation steps, configuration methods and their parameters, upgrade instructions, error reporting check list, or Nazara's behaviour like CLI commands, etc.
  • Contributor Workflows: Everything from code quality, to documentation or steps to contribute, how to set up a dev environment or choose and issue to work on needs to be documented if the process changes.
  • Maintainer & Packaging Procedures: Release workflow and how to update thanix_client in case NetBox changes their API again are the key points here. Also approval criteria for PRs or anything else Maintainers should know.

Who needs to document things?

Documentation is a shared responsibility of all project contributors. Anyone who changes behaviour, packaging, or operations must update relevant documentation as part of the same change. This keeps docs accurate and prevents silent divergence between code and documentation.

Authors should include any relevant documentation updates in their contribution or link to a separate PR that include said updates.

Important for Maintainers

In the event that a contributor links their documentation changes in a separate PR, both PRs are to be treated as one.

Meaning: If the code PR is approved but the docs PR needs changes, neither one is to be merged and vise versa.

Cosmetic changes or strictly code internal ones (like code cleanup, performance improvements, etc.) are generally not covered by the duty to document unless explicitly told otherwise by a maintainer or such a time where architectural decision forms (ADFs) are implemented.

Formatting and Style

Write documentation that is both readable and actionable. Aim for clear, direct sentences and break long topics into smaller pages using headings.

Use examples liberally: A short snipped demonstrating the most common use case is a good actionable example.

The Review Process

Any changes to documentation will be reviewed by maintainers for the quality criteria mentioned below. Only after the review is successfully will the changed be merged. The review validates:

  • Technical accuracy and sufficient depth: The document matches implemented behaviour and guides are executable.
  • Completeness: All affected changes are documented.
  • Readability: The documentation is free of typos or grammatical errors.
  • Style and Inclusivity: Language follows the project's expectations and quality criteria, all images have appropriate alt text descriptions for people using screen readers.

Quality Criteria

All documentation should have a professional and inclusive tone. Gender-neutral language (like they/them or role based nouns like "maintainer" or "developer") should be used. Idioms or jokes should be avoided alongside terms that may be interpreted offensively.

Documentation should go into appropriate detail for the intended target group (i.e. Users or Developers) and not omit any details or become vague when talking about specific topics.

If a feature is not yet implemented or there are currently known bugs or Issues, a "Known Issues" section should be appended at the bottom of the document listing these issues and their corresponding Codeberg Issue if applicable.

Include expected output or other steps which the reader can use to verify operation outcomes in any instructional guide.

Version Compatibility

Currently, the documentation cannot be hosted for individual specific versions of Nazara. Instead, the currently public documentation of Nazara is always valid for the latest release.

Our CI will check for newly created releases and build all changes to the docs/ directory and publish them here.

Examples

Example: Gender Inclusive Language

Instead of

When a user finds a bug, he should open a. issue.

write

If a user discovers a bug, they should open an issue.

Example: Documenting Breaking Changes

Breaking changes should be documented at the top of the file in a warning box.

~~~admonish warning title="v0.2.0: Breaking Change in Configuration"
Since NetBox `v4.5.x`, NetBox requires a different format of API token in the authentication header.
This applies for any new `v2` token. `v1` tokens are unaffected by this.

In your config file, instead of:

    netbox_api_token = "$TOKEN"

you have to write:

    netbox_api_token = "nbt_$KEY.$TOKEN"

Codeberg Issue Reference: [#179](https://codeberg.org/nazara-project/Nazara/issues/179)

The Codeberg Issue Reference can also be a link to a commit that introduced a breaking change between Nazara releases.

Example: Documenting Known Issues

This should be used sparingly as we should focus on fixing these issues instead of documenting them.

However, for anything that a user can face during the use of Nazara or its side projects, which is expected but currently not fixed we should document it at the relevant passage with in a similar style as used above while referencing the corresponding issue where possible.

~~~admonish warning title="v0.2.0: Breaking Change in Configuration"

You could face X here, if so, please do Y. We are working on a fix.

Codeberg Issue Reference: [#000](LINK)

Dependencies

To keep Nazara secure, lightweight, and maintainable, we follow a strict policy on introducing external dependencies.

We only accept external dependencies that meet all of these criteria:

  • Are actively maintained
  • Are essential to solving the given issue
  • Have permissive or at least compatible licenses (MIT, BSD, LGPL, etc.)
  • Are reviewed by maintainers before inclusion

Info

When submitting a PR with a new dependency, please explain why it is needed and why no other alternative was suitable.

Preference Guidelines

We prefer:

  • Standard Library functionality wherever possible
  • Zero-dependency alternatives over large general-purpose crates
  • Lean and well-maintained crates over obscure or overly complex ones

The goal is not to avoid dependencies at all - only to avoid unnecessary, unstable, insecure or high-maintenance ones.

Info

In addition to manual review, we use cargo audit to automatically check, whether our dependencies have known vulnerabilities.

Security Policy

Reporting a Vulnerability

If you discover a security vulnerability in this project, please report it by emailing the project owner. Please provide detailed information about the vulnerability, including steps to reproduce, possible ways of exploitation and any relevant details about your environment.

We appreciate responsible disclosure.

Handling of Vulnerabilities

Once a vulnerability is reported, we will review and prioritize it based on its severity. We aim to have a statement ready as soon as possible and provide updates about its status.

Dependency Security

We use automated workflows to assess the security of our used dependencies. We recommend to use cargo audit to check dependencies which you may be planning to introduce to the project beforehand to avoid your PR to be rejected.

Disclaimer

This security policy is subject to change. It is the responsibility of all project contributors and users to stay updated on any modifications. By participating in this project, you agree to abide by this security policy.

Nazara is designed to run only within your local network. The user is responsible for their network setup themselves.

We are not liable for any damages that are caused by insufficient network security. Security Policy

Becoming a Packager

Projects like Nazara live from people who volunteer to maintain packages for their distributions.

System settings or security policies may prohibit package managers like cargo, pip or npm from installing executables on a system. Or maybe company policies require that all software must only come from OS repos, where they can be monitored and audited.

That's why we need you to help to bring Nazara to new platforms.

If you have a distribution that we do not provide a package for, please follow these steps.


  1. Open a new discussion stating what you want to package for.

  2. Clone the repo and add yourself to the PACKAGERS.md in the project root

Example

Your example entry can look like this:

DistroName/NicknameContact Info (optional)Notes
Arch Linux@urgithuburmail@domain.comAUR maintainer
NixOSSamnone (via GitHub issues)Maintains Nix flake
FedoraBob@bob:somematrix.orgFedora packaging

If you prefer not to share contact details, that's totally fine. We will handle requests regarding your package as GitHub issues.

Danger

Outside contributors: Please be aware that we will list your package as unofficial until you become part of the organization.

If you stop maintaining your package, we reserve the right to remove it from our list of supported distributions.

  1. Open a PR with your changes, and link your request in the PR.

  2. A maintainer will eventually approve or deny your request.

Maintainer Information

This section is information for maintainers of the Nazara Project. It documents workflows and guides that are okay to be public.

Release Workflow

When it's time to make a new release of Nazara, this procedure should be followed.

Thanix Maintainer Info

Most of this information is also valid for maintainers of Thanix.

1. Update all Version numbers

Make sure to update all version numbers in the following places:

  • Cargo.toml
  • Nazara.spec
  • README.md (Add to Compatibility List)
  • docs/src/intro.md (Add to Compatibility List)

1.1 Version Numbers

We generally orient ourselves along the lines of semantic versioning. The following guidelines apply for versioning:

  • Patch Versions: Smaller changes/updates/bugfixes without updates to the API client
  • Minor Versions: Updates to the API client without major reworks/breaking changes
  • Major Versions: Breaking Changes with the API client OR completion of a large feature that significantly impacts Nazara's behaviour or expands scope.

Info

In the end, decisions about versioning are made by the core maintainers. When in doubt, shoot an email to the project owner.

2. Tag New Version

Tag a new version on the latest commit on the main branch.

$ git tag -a v0.1.0_beta.1 -m "v0.1.0_beta.1: Feature X"
$ git push $REMOTE v0.1.0_beta.1

$REMOTE here is the name of the upstream Nazara remote. By default, when cloning upstream, it's origin.

3. Build and Publish to crates.io

Info

We always release to crates.io first.

Publish the newest version of Nazara to crates.io.

$ cargo publish

Warning

In order to be able to publish new versions, you must be given permissions to do so first. If the publishing fails, please reach out to the Project Owner.

4. Create a New Release

The next step includes creating a new Release via the VC Web UI.

To do so, follow these steps:

  • Select latest tag
  • Select previous tag (if not already done automatically)
  • Press "Generate Release Notes" and split into Added, Fixed, Removed sections as it seems fitting
  • Describe the core changes of this release in a short What's new? section at the top
  • Manually build a .deb package using cargo deb and attach that and the release binary to the Release for people who want to manually download it
  • Hit "Create Release"

5. Updating Distribution Packages

-- Coming Soon --

API Update Workflow

From time to time, it can happen that Nazara becomes incompatible with the newest NetBox release.

Follow these steps in order to bring Nazara up to speed again.

To do this, you will need to install Thanix and clone the thanix_client repository.

1. Create Version Branch

The first thing we do, is lock the current version into a new branch. This ensures we have a fallback, and allows users to patch the version that may still be compatible to their running NetBox instance.

To do this, use this format:

$ git checkout -b version/alpha-2

Or for full release versions:

$ git checkout -b version/vX.Y.Z

2. Update thanix_client

thanix_client is the crate we use to handle API requests to and from NetBox. It is generated by Thanix using the current upstream NetBox API schema.

2.1 Get the latest API schema

Get the latest API schema YAML (Download may take a couple seconds)

2.2 Generate new thanix_client crate

Throw the downloaded YAML file into Thanix to generate a new crate. You will get a new directory wherever you executed it containing a completely new thanix_client repo.

Take the src directory from that output and copy it, in its entirety, into the thanix_client repository you cloned from GitHub. If prompted, agree to overwrite all existing files.

Update the version numbers in thanix_client accordingly.

Test build the crate by running:

$ cargo build --release

This may take a while depending on your system. If all is good, publish it to crates.io:

$ cargo publish

3. Perform Port of Nazara

First thing you need to do is to update the thanix_client version number in Nazara's Cargo.toml file.

It is almost guaranteed that Nazara won't build after updating that API client. This means that you have to go and fix all the issues that arise. Most likely these will be linked to the payloads and their corresponding fields (like it was moving from 3.x to 4.x).

You will have to fix these in order to fully port Nazara to the newest NetBox version.

4. Follow Release Workflow

Ports of the API client like these are always a separate minor release.

After the codebase is updated, and has been tested and verified and the associated PRs merged, a new release will have to be created.

For this, follow the release workflow.

User Documentation

This documentation is supposed to help you get started with Nazara.

Danger

Nazara requires root privileges to function!

Installing Nazara

You can install Nazara in a bunch of different ways. Our recommended solutions is to either get it from crates.io, or build the latest release from source yourself.

Native Packages

We are currently working on building distribution packages in the future with the first ones being targeted at openSUSE Tumbleweed, Slowroll, Leap and SLES16.

If you would like to build a package for your distribution, please refer to our packager's guide.

Installing via crates.io

To install Nazara via Rust's package index, make sure you have cargo and a current Rust toolchain installed.

Then in your Terminal, run

cargo install --locked nazara

After installation you should be able to run Nazara just like you would any other program. If it doesn't work, it is likely that cargo's bin directory is not in your path. Refer to cargo's documentation for help with that problem.

Building from Source

For this, please make sure you have cargo, libopenssl and a current Rust toolchain installed. (The last of which should be compatible with Rust edition 2024).

Simply clone the repository and run cargo build to build it yourself.

git clone https://codeberg.org/nazara-project/Nazara && cd Nazara
cargo build --release

This process may take a while, mainly thanks to our API client library thanix_client.

Once completed you have a portable binary at ./target/release/nazara.

Native Packages

We are currently at work to provide native Linux packages starting with the openSUSE family of Linux distributions.

Once we make progress on this front, you will find this information here.

Release Attachments

We currently provide a pre-built binary as well as a Debian package attached to the latest release and also plan on attaching these to every release going forward as an easy way to download and install Nazara.

Visit our release page to find these for yourself.

Installing the .deb Package

To install the .deb package you downloaded from the Release, use the dpkg command.

sudo dpkg -i nazara_0.1.0-1_amd64.deb

Configuration

v0.2.0: Breaking Change in Configuration with NetBox v4.5.x

Since NetBox v4.5.x, NetBox requires a different format of API token in the authentication header. This applies for any new v2 token. v1 tokens are unaffected by this.

In your config file, instead of:

netbox_api_token = "$TOKEN"

you have to write:

netbox_api_token = "nbt_$KEY.$TOKEN"

The $KEY value can be found in the corresponding "Key" field in the Netbox WebUI when looking at the API key entry.

We are currently looking into updating our documentation and API client.

Codeberg Issue Reference: #179

Nazara supports two ways of providing configuration parameters: CLI arguments and a configuration file.

Nazara accepts these parameters from you:

  • -d, --dry-run: Print all collected information without committing it to NetBox.
  • -u, --uri <URI>: URI to your NetBox instance.
  • -t, --token <TOKEN>: Your API authentication token.
  • -p, --plugin <PLUGIN>: The path to a plugin script you want to use to fill in custom fields.
  • -h, --help: Print help.
  • -V, --version: Print version.
  • --log-level: Changes the level of status messages to be printed to the terminal. Defaults to debug.

Hint: Log Levels

Valid values for --log-level are:

  • trace
  • debug
  • info
  • warn
  • error
  • fatal (suppresses all output except for fatal errors)

Afterwards, Nazara expects one of the following operation types to be specified:

  • register: Register a new device or vm in NetBox.
  • update --id <device_id>: To update an existing device or vm.
  • auto: Let Nazara decide based on the machine's name and serial number whether registration or update is needed.

Configuring via CLI

For the most detailed guide on how to configure nazara via CLI, please make use of the help function.

sudo nazara --help

When launching Nazara for the first time, a configuration file will be written at $HOME/.config/nazara/config.toml. You can use CLI parameters to override your settings in the config file.

Nazara's configuration must be located in the root user's home directory at $HOME/.config/nazara/config.toml.

When launching Nazara for the first time, it will write a stock config file to that path. Certain parameters are required to be configured there manually.

Aside from the NetBox system parameters, configuration via the config.toml also allows you to add certain custom fields to your system information that cannot be automatically collected. Please check the example file below for exact information about which options are possible.

Note

Currently, configuration by config file is the proper way to use Nazara given the amount of data required to register a machine. We are investigating possibilities to make this less of a hassle. In the meantime, we suggest you copy-paste the config between machines of the same type and function.

# Template nazara-config.toml file for v0.1.0-beta.3

# Configuration parameters for the NetBox connection
[netbox]
netbox_api_token = ""
netbox_uri = ""

# Common settings that always have to be provided.
[common]
# Custom name of the device or VM. (optional, fallback: hostname)
# You can concatenate this name with the hostname by ending it with '@'.
name = ""
description = ""
# A comment left by Nazara if anything gets modified by it.
comments = "Automatically registered by Nazara."
# The current status of the device/VM.
status = "active"
# The IP you want to set as primary (optional)
primary_ip4 = ""
primary_ip6 = ""

# ---------------------------------------------------------------
# Use [device] for devices, or [vm] if this is a virtual machine.
# ---------------------------------------------------------------

[device]
device_type = 0
role = 0
site = 0

# [vm]
# cluster = 0

For VMs, the name parameter is required to be able to distinguish them cleanly.

Tip

The name parameter is optional for devices. If left empty, Nazara will assume the system's hostname as the name value for the entry. You can combine both your custom name and the machine's hostname by fixing a @ symbol to the end of the name value. This works on both VMs and devices.

This way a config entry like this:

[common]
name = "aurora@"

turns into:

aurora@linux.fritz.box

in the final entry.

The config commands

Nazara provides you with several commands to manage your configuration files:

  • write-config: Write a new config file or overwrite an existing one.
  • check-config: Validate if your config is still valid.
  • view-config: Print config to console.

The write-config allows you to change individual parameters, or perform a bulk update by passing a JSON structure via CLI. These options are exclusive. Passing both is disallowed.

Example: Using write-config to change config parameters

These examples show you how you can edit your config file from the command line.

# Pass arguments individually
sudo nazara write-config --uri https://netbox.sampleorg.com

Or in batches using JSON:

sudo nazara write-config --json '{
  "netbox": {
    "netbox_uri": "https://netbox.example.com",
    "netbox_api_token": "abcd1234"
  },
  "common": {
    "name": "test-device",
    "description": "A physical test machine",
    "comments": "Created for testing purposes",
    "status": "active"
  },
  "device": {
    "device_type": 1,
    "role": 2,
    "site": 3
  }
}'

Please note that this section is still a work in progress and all information is subject to change.

Registering a Device or VM

Registering a device with Nazara is as straight forward as setting up the config file and running a simple command.

Info

Please make sure you have sudo privileges on the machine or VM you want to register, otherwise DMI information collection will fail.

Example Registration Workflow (Physical Device)

On a physical device, a example registration workflow may look like this:

1. Set up Configuration

The user wrote a configuration file at /root/.config/nazara/config.toml

Example: Device Config

# Configuration parameters for the NetBox connection
[netbox]
netbox_api_token = "XXXXXXXX"
netbox_uri = "https://netbox.organisation.com"

# Common settings that always have to be provided
[common]
# The name of the device/VM
name = "user@"
description = ""
# A comment left by Nazara if anything gets modified by it.
comments = "Automatically registered by Nazara."
# The current status of the device/VM
status = "active"
primary_ip4 = "192.168.0.1"
primary_ip6 = ""

# ---------------------------------------------------------------
# Use [device] for devices, or [vm] if this is a virtual machine.
# ---------------------------------------------------------------

[device]
device_type = 1
role = 1
site = 1

The configuration file for a VM looks quite similarly. For a list of allowed config parameters, please refer to our template.

You can verify the integrity of your config by running nazara check-config.

2. Run nazara register

To register your device, simply run

sudo nazara register

This will register your device, its interfaces and ip-addresses statically in NetBox.

If you are in an environment where IP addresses are managed by DHCP, Nazara offers several DHCP modes to handle this.

To do so, pass the --ip-mode to either the register or update command to switch between modes.

DHCP Compatibility

static Mode

Default behaviour of Nazara. Simply registers everything without paying attention to any environments.

This may crash if the device's IP addresses change, no reconciliation will take place.

sudo nazara register --ip-mode static

dhcp-ignored Mode

In cases where a DHCP servers syncs IP addresses with NetBox, any registration or update of IP addresses will be skipped.

sudo nazara register --ip-mode dhcp-ignored

dhcp-observed Mode

The most complex mode, this will register the device or VM with all IP addresses, as they are currently discovered. Nazara will try to reconcile the IP addresses it discovers, with what is present in NetBox.

If the address does not exist, it will be created, if it is assigned to a different interface, it will be reassigned.

The IP addresses will be tagged with dhcp tags.

sudo nazara register --ip-mode dhcp-observed

This mode is to be used in cases where Netbox itself manages available IP addresses and a DHCP server syncs from that.

Preparing the NetBox environment

Before registering your Device, Nazara verifies that required entities exist in NetBox (site, role, device_type, etc.) and that the tags nazara and dhcp that we use to identify our entries are present.

If you are running Nazara for the first time, you may need to prepare your NetBox environment. The --prepare-environment flag will automatically create required tags if they don't exist.

sudo nazara register --prepare-environment ...

This will:

  • Verify site, role, device_type etc. IDs are valid and exist
  • Create the nazara tag (used to mark all Nazara-created entries)
  • Create the dhcp tag (used for DHCP-observed IP addresses)

You can also prepare your environment without registering:

sudo nazara prepare-environment

Common Issues

TOML Deserialization Error

This is most likely an issue with your config file. Make sure it has correct TOML syntax and all the fields specified in the template are present in your example.

Unexpected Response

We know this output is ugly but please bear with us we are on it. Check the output for a status code:

  • 400 - Bad Request: This usually means that Nazara's payload was rejected by NetBox. Most commonly this means that you want to assign this device to a role, device type or site that does not exist (invalid ID). It can also mean that the device already exists so check if a device with the configured name has already been registered.

File OP error

This usually indicates that Nazara cannot open the config file at /root/.config/nazara/config.toml it's either missing, or you forgot to run Nazara as root.

Any missing?

You have issues with registering your device and don't know how to proceed?

This could be a bug! Please report it.

Updating a Device or VM

If you have already registered you Device or VM in NetBox, you can update the entry using the nazara update command.

For this, you need to check if your configuration is still valid and up-to-date. Use the write-config command to change the info you want to update.

Info

This process also requires sudo privileges, please make sure you have them before attempting an update.

This command will try to update any information that has been changed in a PATCH request. Information that has not changed will not be touched.

Example Updating Workflow (Physical Device)

1. Update Configuration File

Make sure your config file at /root/.config/nazara/config.toml is up to date.

If you want to change any parameter you can do so by using the write-config command.

For example, we want to switch the device's site from id=1 to id=2.

sudo nazara write-config --site 2

This will update the entry in the configuration file.

2. Update the Entry

Now, to update the entry, go and get the entry's ID from NetBox. You can see it in the URL of your browser. In our case: http://localhost:8000/dcim/devices/57/ (device id: 57)

Then simply run

sudo nazara update --id 57

This will then update the entry for you.

DHCP Compatibility

static Mode

Default behavior of Nazara. Simply registers everything without paying attention to any environments.

This may crash if the device's IP addresses change, no reconciliation will take place.

sudo nazara register --ip-mode static

dhcp-ignored Mode

In cases where a DHCP servers syncs IP addresses with NetBox, any registration or update of IP addresses will be skipped.

sudo nazara register --ip-mode dhcp-ignored

dhcp-observed Mode

The most complex mode, this will register the device or VM with all IP addresses, as they are currently discovered. Nazara will try to reconcile the IP addresses it discovers, with what is present in NetBox.

If the address does not exist, it will be created, if it is assigned to a different interface, it will be reassigned.

The IP addresses will be tagged with dhcp tags.

sudo nazara register --ip-mode dhcp-observed

This mode is to be used in cases where Netbox itself manages available IP addresses and a DHCP server syncs from that.

Preparing the NetBox environment

Before registering your Device, Nazara verifies that required entities exist in NetBox (site, role, device_type, etc.) and that the tags nazara and dhcp that we use to identify our entries are present.

If you are running Nazara for the first time, you may need to prepare your NetBox environment. The --prepare-environment flag will automatically create required tags if they don't exist.

sudo nazara register --prepare-environment ...

This will:

  • Verify site, role, device_type etc. IDs are valid and exist
  • Create the nazara tag (used to mark all Nazara-created entries)
  • Create the dhcp tag (used for DHCP-observed IP addresses)

You can also prepare your environment without registering:

sudo nazara prepare-environment

Common Issues

IP "X.X.X.X" has not been registered in NetBox

This is a common issue with environments that use DHCP with frequently changing IP addresses. In this case the new IP address has not been registered with NetBox beforehand, so there is no IP entry to update.

In this case, you must register that new IP manually in NetBox as the update process is forbidden from doing so.

Alternatively, you can delete and re-register the device.¹

We are actively working on a managed mode that forbids Nazara's IP management altogether in cases where you have a different source of truth for them.

¹ Not recommended if you touch your device entries manually.

The Plugin System

Warning

This feature is highly experimental, but should work fine for simple custom fields.

Nazara allows you to fill out the "Custom Fields" section of your device or VM entries by using a custom bash script.

This bash script should return your desired information as JSON compliant string.

Using custom scripts with Nazara

To do so, you can provide Nazara with the path to your custom script using the --plugin argument.

nazara --plugin ~/.config/nazara/scripts/custom_script.sh register

Nazara will run your script and expect to get a JSON string back, which will be parsed into a HashMap<String, Value>

Note, that we currently only support parameters which have been specified in NetBox as string type.

You can find an example script here.

Danger

Please make sure that your script models the custom fields exactly like they are in your NetBox instance, as we have no way of verifying it.