homelab/services/device-inventory/src/server/database.h
Dan V e1a482c500 feat(device-inventory): add hooks system and DNS updater hook
- Add Hook interface (filter + execute contract) in server/hooks/hook.h
- Add HookRunner in server/hooks/hook_runner.h: spawns a detached thread
  per matching hook, with try/catch protection against crashes
- Add DnsUpdaterHook in server/hooks/dns_updater_hook.{h,cpp}:
  triggers on server name changes, logs in to Technitium, deletes the
  old A record (ignores 404), and adds the new A record
  Config via env vars: TECHNITIUM_HOST/PORT/USER/PASS/ZONE, DNS_TTL
- Add Database::get_nics_for_server() to resolve a server's IPv4 address
- Wire HookRunner into InventoryServer; cmd_edit_server now fires hooks
  with before/after Server snapshots
- Update CMakeLists.txt to include dns_updater_hook.cpp
- Document env vars in Dockerfile

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 14:58:36 +01:00

98 lines
5.5 KiB
C++

#pragma once
#include <cstdint>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include "common/models.h"
// ─────────────────────────────────────────────────────────────────────────────
// Database
// Thread-safe, file-backed inventory store.
//
// File format (text, one record per line):
// # device-inventory database
// META|next_server_id|next_part_type_id|next_part_id
// S|id|name|hostname|location|description
// PT|id|name|description
// MS|part_id|server_id|serial_or_NULL|last_updated|speed_mhz|size_mb|manufacturer|channel_config|other_info
// MSLOT|part_id|server_id|serial_or_NULL|last_updated|allowed_speed_mhz|allowed_size_mb|installed_stick_id_or_NULL
// CPU|part_id|server_id|serial_or_NULL|last_updated|name|manufacturer|speed_ghz|cores|threads
// CPUSLOT|part_id|server_id|serial_or_NULL|last_updated|form_factor|installed_cpu_id_or_NULL
// DISK|part_id|server_id|serial_or_NULL|last_updated|manufacturer|model|generation|conn_type|conn_speed|disk_speed|disk_size|disk_type|age_years|partition_ids_LS|partition_sizes_LS|vm_hostnames_LS|vm_server_ids_LS
// NIC|part_id|server_id|serial_or_NULL|last_updated|manufacturer|model|age_years|conn_type|conn_speed|mac|ips_LS|dhcp
//
// Lists within a field use LS ('\x02'). Pipe and backslash in text fields are
// escaped via escape()/unescape().
// ─────────────────────────────────────────────────────────────────────────────
class Database {
public:
explicit Database(std::string path);
// Persist current inventory to disk (acquires lock)
bool save();
// Load (or reload) inventory from disk
bool load();
// ── Servers ───────────────────────────────────────────────────────────────
Server add_server(std::string name, std::string hostname,
std::string location, std::string description);
std::vector<Server> list_servers() const;
std::optional<Server> get_server(uint32_t id) const;
bool edit_server(uint32_t id, const std::string& field,
const std::string& value);
bool remove_server(uint32_t id);
// ── Part types (labels) ───────────────────────────────────────────────────
PartType add_part_type(std::string name, std::string description);
std::vector<PartType> list_part_types() const;
std::optional<PartType> get_part_type(uint32_t id) const;
bool edit_part_type(uint32_t id, const std::string& field,
const std::string& value);
bool remove_part_type(uint32_t id);
// ── Typed parts ───────────────────────────────────────────────────────────
// kv is a map of field-key → field-value strings (K_* constants from protocol.h)
uint32_t add_part(const std::string& type_name, uint32_t server_id,
const std::map<std::string,std::string>& kv);
// Insert or update: for nic match by mac, for others by serial
uint32_t upsert_part(const std::string& type_name, uint32_t server_id,
const std::map<std::string,std::string>& kv);
bool edit_part(uint32_t part_id, const std::map<std::string,std::string>& kv);
bool remove_part(uint32_t part_id);
// Wire-row format: type_name<FS>part_id<FS>server_id<FS>serial_or_NULL<FS>last_updated<FS>k1<FS>v1...
std::vector<std::string> list_parts(uint32_t server_id,
const std::string& type_filter = "") const;
std::string get_part_row(uint32_t part_id) const; // empty = not found
// Returns all NetworkCard records belonging to the given server.
std::vector<NetworkCard> get_nics_for_server(uint32_t server_id) const;
private:
std::string path_;
Inventory inv_;
mutable std::mutex mu_;
// Internal save without acquiring lock (caller must hold mu_)
bool save_nolock();
// Internal add_part without acquiring lock
uint32_t add_part_nolock(const std::string& type_name, uint32_t server_id,
const std::map<std::string,std::string>& kv);
uint32_t alloc_part_id(); // call while holding mu_
static std::string escape(const std::string& s);
static std::string unescape(const std::string& s);
static void apply_base(PartBase& p, const std::map<std::string,std::string>& kv);
// Per-type wire-row serializers
std::string serialize_memory_stick(const MemoryStick& m) const;
std::string serialize_memory_slot (const MemorySlot& m) const;
std::string serialize_cpu (const CPU& c) const;
std::string serialize_cpu_slot (const CPUSlot& c) const;
std::string serialize_disk (const Disk& d) const;
std::string serialize_nic (const NetworkCard& n) const;
};