homelab/services/device-inventory
Dan V 29440b68a9 fix: sudo dmidecode fallback when running without root
Without root, dmidecode exits 0 but outputs only a header comment
with no Handle blocks (DMI tables are root-only in sysfs).
The previous empty-string check never triggered the sudo retry.

Now checks for the presence of 'Handle ' lines: if absent, retries
transparently with sudo. Users with passwordless sudo get full hardware
detail (CPU slots, memory sticks/slots, cache, voltage) without needing
to explicitly invoke sudo themselves.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-06 23:45:35 +02:00
..
src fix: sudo dmidecode fallback when running without root 2026-04-06 23:45:35 +02:00
web-ui feat(device-inventory): add management web UI and pciutils for NIC discovery 2026-03-31 22:42:20 +02:00
build-and-load.sh feat(device-inventory): add hooks system and DNS updater hook 2026-03-24 14:58:36 +01:00
build-cli.sh feat(inventory-cli): build script, manpage help, discover-only command 2026-04-01 00:49:17 +02:00
CMakeLists.txt feat(device-inventory): add hooks system and DNS updater hook 2026-03-24 14:58:36 +01:00
Dockerfile feat(device-inventory): add hooks system and DNS updater hook 2026-03-24 14:58:36 +01:00
Dockerfile.cli feat(device-inventory): add management web UI and pciutils for NIC discovery 2026-03-31 22:42:20 +02:00
instructions.md feat(device-inventory): add hooks system and DNS updater hook 2026-03-24 14:58:36 +01:00
README.md feat(device-inventory): add hooks system and DNS updater hook 2026-03-24 14:58:36 +01:00

device-inventory

A lightweight hardware inventory tracker built in C++17. Uses a client/server model: the server owns the file-backed database and exposes a TCP interface on localhost; the CLI client connects to it to run commands.


Architecture

┌──────────────┐  TCP (localhost:9876)  ┌─────────────────────┐
│ inventory-cli│ ─────────────────────► │ inventory-server    │
│  (CLI client)│ ◄───────────────────── │  database ► file.db │
└──────────────┘                        └─────────────────────┘
  • inventory-server loads the inventory file on start, accepts one command per connection, and flushes changes to disk after every mutation.
  • inventory-cli a thin CLI that translates subcommands into protocol messages, connects to the server, and pretty-prints the response.

Build

Requires CMake ≥ 3.16 and a C++17-capable compiler.

cd device-inventory
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

Binaries end up in build/inventory-server and build/inventory-cli.


Running

Start the server

./build/inventory-server                       # uses inventory.db in CWD
./build/inventory-server --port 9876 --db /var/lib/inventory/data.db

The server reloads the file every time it restarts. On the very first run the file is created automatically.

Use the CLI

# Shorthand for the examples below
cli="./build/inventory-cli"

Commands

Servers

# Add a server
$cli add-server "web01" "192.168.1.10" "Rack A" "Primary web server"

# List all servers
$cli list-servers

# Edit a field (fields: name | hostname | location | description)
$cli edit-server 1 hostname "10.0.0.1"
$cli edit-server 1 description "Retired  do not use"

Part types

# Add a part type
$cli add-part-type "RAM" "Memory modules"
$cli add-part-type "SSD" "Solid-state storage"

# List all part types
$cli list-part-types

# Edit a field (fields: name | description)
$cli edit-part-type 1 description "DDR4/DDR5 memory"

Parts

# Add a part to a server
# add-part <server_id> <part_type_id> <name> <serial> <description>
$cli add-part 1 1 "16 GB DDR4" "SN-MEM-001" "DIMM slot A1"
$cli add-part 1 2 "Samsung 870 EVO 1 TB" "SN-SSD-004" "Primary OS drive"

# List parts of a server
$cli list-parts 1

# Edit a field (fields: name | serial | description | server_id | part_type_id)
$cli edit-part 2 serial "SN-SSD-005"
$cli edit-part 2 server_id 3

Database file format

Plain text, one record per line safe to inspect or diff with standard tools.

# device-inventory database
META|<next_server_id>|<next_part_type_id>|<next_part_id>
S|<id>|<name>|<hostname>|<location>|<description>
PT|<id>|<name>|<description>
P|<id>|<server_id>|<part_type_id>|<name>|<serial>|<description>

Pipe characters and backslashes inside field values are escaped with \| and \\ respectively.


Wire protocol

Commands are newline-terminated lines; fields are separated by ASCII SOH (0x01). Each connection carries exactly one request/response pair.

Client → Server:  COMMAND\x01arg1\x01arg2\n
Server → Client:  OK\n
                  field1\x01field2\x01...\n   ← 0 or more data rows
                  END\n
         -- or --
                  ERR <message>\n