homelab/services/device-inventory/src/server/hooks/dns_updater_hook.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

79 lines
3.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "server/hooks/hook.h"
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netdb.h>
#include <sstream>
#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
// ─────────────────────────────────────────────────────────────────────────────
// DnsUpdaterHook
//
// Fires whenever a server's *name* changes. Updates Technitium DNS by:
// 1. Logging in → GET /api/user/login?user=…&pass=… → token
// 2. Deleting the old A record (404 errors are silently ignored)
// 3. Adding the new A record
//
// The server's IP is derived from the first NIC ip_address that belongs to the
// server record. This hook receives the old and new Server structs; to look up
// IP addresses it needs access to the Database pass a const ref in the ctor.
//
// Config is supplied via environment variables so no secrets land in source:
// TECHNITIUM_HOST (default: 192.168.2.193)
// TECHNITIUM_PORT (default: 5380)
// TECHNITIUM_USER (default: admin)
// TECHNITIUM_PASS (required hook logs an error and skips if unset)
// TECHNITIUM_ZONE (default: homelab)
// DNS_TTL (default: 300)
// ─────────────────────────────────────────────────────────────────────────────
class Database; // forward declaration full header included in .cpp
class DnsUpdaterHook : public Hook {
public:
explicit DnsUpdaterHook(const Database& db) : db_(db) {}
// Trigger only when the server name changes.
bool filter(const Server& old_server, const Server& new_server) override {
return old_server.name != new_server.name;
}
void execute(const Server& old_server, const Server& new_server) override;
private:
const Database& db_;
// Resolve config from environment, falling back to defaults.
struct Config {
std::string host;
int port;
std::string user;
std::string pass;
std::string zone;
int ttl;
};
static Config load_config();
// Percent-encode a string for use in a URL query parameter.
static std::string url_encode(const std::string& s);
// Open a TCP connection; throws on failure.
static int connect_to(const std::string& host, int port);
// Send an HTTP/1.0 GET request and return the full response body.
static std::string http_get(const std::string& host, int port,
const std::string& path);
// Send an HTTP/1.0 POST request (application/x-www-form-urlencoded body)
// and return the full response body.
static std::string http_post(const std::string& host, int port,
const std::string& path,
const std::string& body);
// Minimal JSON value extractor finds "key":"value" or "key":value.
static std::string json_get(const std::string& json, const std::string& key);
};