libutil/systemd/hostnamectl: init

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 <raito@lix.systems>
This commit is contained in:
Raito Bezarius
2026-07-03 17:45:26 +02:00
committed by rootile
parent e9a01a5be6
commit ad199c4723
3 changed files with 145 additions and 0 deletions
+2
View File
@@ -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',
+58
View File
@@ -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<Result<std::optional<HostInformation>>> get_host_information()
try {
auto output = TRY_AWAIT(runProgram("hostnamectl", true, {"--json=short"}));
SystemdHostname raw = json::parse(output);
std::optional<std::string> 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
+85
View File
@@ -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<std::string> 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<std::string> 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<JSON> osRelease = (std::vector<JSON>) 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<Result<std::optional<HostInformation>>> get_host_information();
};
};
#endif