From ad199c47236b4cf2473bf90536287b6cdba45fad Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sun, 28 Jun 2026 01:41:42 +0200 Subject: [PATCH] libutil/systemd/hostnamectl: init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is some code that is specific to systemd to fetch hostnamectl information via the JSON flag. This will be used for `nix doctor`. Errors will show up similar to this: ``` ❯ ./build/lix/nix/nix doctor [INFO] Nix system type: 'x86_64-linux' warning: could not get host information via hostnamectl: error: Expected JSON value to be of type 'string' but it is of type 'number' ``` and makes them non-fatal. Change-Id: Idd39f29626e770b1cd3896e8eb8545416c74b041 Signed-off-by: Raito Bezarius --- lix/libutil/meson.build | 2 + lix/libutil/systemd.cc | 58 ++++++++++++++++++++++++++++ lix/libutil/systemd.hh | 85 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 lix/libutil/systemd.cc create mode 100644 lix/libutil/systemd.hh diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index b5f882b8b..452432c2c 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -45,6 +45,7 @@ liblix_sources += files( 'source-path.cc', 'strings.cc', 'suggestions.cc', + 'systemd.cc', 'tarfile.cc', 'terminal.cc', 'thread-name.cc', @@ -139,6 +140,7 @@ libutil_headers = files( 'strings.hh', 'suggestions.hh', 'sync.hh', + 'systemd.hh', 'tarfile.hh', 'terminal.hh', 'thread-name.hh', diff --git a/lix/libutil/systemd.cc b/lix/libutil/systemd.cc new file mode 100644 index 000000000..1b9b4c1bb --- /dev/null +++ b/lix/libutil/systemd.cc @@ -0,0 +1,58 @@ +#if __linux__ + +#include "libutil/systemd.hh" +#include "libutil/json-fwd.hh" +namespace nix { + +namespace systemd { + +void from_json(const JSON & j, SystemdHostname & h) +{ + h = SystemdHostname::parse(j); +} + +kj::Promise>> get_host_information() +try { + auto output = TRY_AWAIT(runProgram("hostnamectl", true, {"--json=short"})); + SystemdHostname raw = json::parse(output); + std::optional buildId; + + for (const auto & line : raw.OperatingSystemReleaseData) { + if (line.starts_with("BUILD_ID=")) { + buildId = line.substr(9); + + // Clean up quotes if they are present. + if (buildId->size() >= 2 && buildId->front() == '"' && buildId->back() == '"') { + buildId = buildId->substr(1, buildId->length() - 2); + } + break; + } + } + + HostInformation host_info = { + .hostname = raw.Hostname, + .chassis = raw.Chassis, + .kernel_release = raw.KernelRelease, + .kernel_version = raw.KernelVersion, + .os_pretty_name = raw.OperatingSystemPrettyName, + .hardware_vendor = raw.HardwareVendor, + .hardware_model = raw.HardwareModel, + .hardware_version = raw.HardwareVersion, + .firmware_vendor = raw.FirmwareVendor, + .firmware_version = raw.FirmwareVersion, + .firmware_date = raw.FirmwareDate, + .build_id = buildId, + }; + + co_return {host_info}; +} catch (Error & e) { + printTaggedWarning("could not get host information via hostnamectl: %s", e.msg()); + co_return {{}}; +} catch (...) { + co_return result::current_exception(); +} +}; + +}; + +#endif diff --git a/lix/libutil/systemd.hh b/lix/libutil/systemd.hh new file mode 100644 index 000000000..e4446e714 --- /dev/null +++ b/lix/libutil/systemd.hh @@ -0,0 +1,85 @@ +#pragma once +///@file + +#if __linux__ + +#include "lix/libutil/types.hh" +#include "lix/libutil/json.hh" +#include "lix/libutil/processes.hh" +#include "lix/libutil/logging.hh" + +namespace nix { + +namespace systemd { + +// Machine readable information returned by the hostname component from systemd (hostnamectl). +struct HostInformation +{ + std::string hostname; + std::string chassis; + std::string kernel_release; + std::string kernel_version; + std::string os_pretty_name; + std::string hardware_vendor; + std::string hardware_model; + std::string hardware_version; + std::string firmware_vendor; + std::string firmware_version; + std::string firmware_date; + std::optional build_id; +}; + +// Internal data structure that systemd returns for hostnamectl. +// This needs to be kept update with the range of supported versions of systemd for Lix. +struct SystemdHostname +{ + std::string Hostname; + std::string Chassis; + std::string KernelRelease; + std::string KernelVersion; + std::string OperatingSystemPrettyName; + std::vector OperatingSystemReleaseData; + std::string HardwareVendor; + std::string HardwareModel; + std::string HardwareVersion; + std::string FirmwareVersion; + std::string FirmwareVendor; + std::string FirmwareDate; + + static SystemdHostname parse(const JSON & j) + { + using nlohmann::detail::value_t; + + SystemdHostname raw; + + ensureType(j, value_t::object); + raw.Hostname = ensureType(valueAt(j, "Hostname"), value_t::string); + raw.Chassis = ensureType(valueAt(j, "Chassis"), value_t::string); + raw.KernelRelease = ensureType(valueAt(j, "KernelRelease"), value_t::string); + raw.KernelVersion = ensureType(valueAt(j, "KernelVersion"), value_t::string); + raw.OperatingSystemPrettyName = ensureType(valueAt(j, "OperatingSystemPrettyName"), value_t::string); + raw.HardwareVendor = ensureType(valueAt(j, "HardwareVendor"), value_t::string); + raw.HardwareModel = ensureType(valueAt(j, "HardwareModel"), value_t::string); + raw.HardwareVersion = ensureType(valueAt(j, "HardwareVersion"), value_t::string); + raw.FirmwareVersion = ensureType(valueAt(j, "FirmwareVersion"), value_t::string); + raw.FirmwareVendor = ensureType(valueAt(j, "FirmwareVendor"), value_t::string); + raw.FirmwareDate = + std::to_string((uint64_t) ensureType(valueAt(j, "FirmwareDate"), value_t::number_unsigned)); + const JSON & osReleaseRaw = valueAt(j, "OperatingSystemReleaseData"); + ensureType(osReleaseRaw, value_t::array); + std::vector osRelease = (std::vector) osReleaseRaw; + for (const auto & item : osRelease) { + raw.OperatingSystemReleaseData.push_back(ensureType(item, value_t::string)); + } + + return raw; + } +}; + +void from_json(const JSON & j, SystemdHostname & h); + +kj::Promise>> get_host_information(); +}; +}; + +#endif