fix: manual host creation and improve host identification

- Add machine_id support for manual host creation from GUI
- Generate temporary 'pending-{uuid}' machine_id for new hosts
- Agent now collects and sends machine_id on every update
- Backend replaces pending machine_id with real one on first agent connection
- Remove unnecessary duplicate name check (friendly_name can be duplicated)
- Add get_machine_id() function to agent (reads from /etc/machine-id, /var/lib/dbus/machine-id, or generates fallback)
- Display IP address in Network tab on host details page
- Fix network tab visibility conditions to include host.ip

This ensures proper host identification using machine_id while maintaining backwards compatibility with API credentials as the primary authentication method.
This commit is contained in:
Muhammad Ibrahim
2025-10-04 09:39:47 +01:00
parent 35d3c28ae5
commit dd28e741d4
3 changed files with 51 additions and 11 deletions

View File

@@ -56,6 +56,28 @@ warning() {
log "WARNING: $1" log "WARNING: $1"
} }
# Get or generate machine ID
get_machine_id() {
# Try standard locations for machine-id
if [[ -f /etc/machine-id ]]; then
cat /etc/machine-id
elif [[ -f /var/lib/dbus/machine-id ]]; then
cat /var/lib/dbus/machine-id
else
# Fallback: generate from hardware UUID or hostname+MAC
if command -v dmidecode &> /dev/null; then
local uuid=$(dmidecode -s system-uuid 2>/dev/null | tr -d ' -' | tr '[:upper:]' '[:lower:]')
if [[ -n "$uuid" && "$uuid" != "notpresent" ]]; then
echo "$uuid"
return
fi
fi
# Last resort: hash hostname + primary MAC address
local primary_mac=$(ip link show | grep -oP '(?<=link/ether\s)[0-9a-f:]+' | head -1 | tr -d ':')
echo "$HOSTNAME-$primary_mac" | sha256sum | cut -d' ' -f1 | cut -c1-32
fi
}
# Check if running as root # Check if running as root
check_root() { check_root() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
@@ -865,6 +887,9 @@ send_update() {
# Merge all JSON objects into one # Merge all JSON objects into one
local merged_json=$(echo "$hardware_json $network_json $system_json" | jq -s '.[0] * .[1] * .[2]') local merged_json=$(echo "$hardware_json $network_json $system_json" | jq -s '.[0] * .[1] * .[2]')
# Get machine ID
local machine_id=$(get_machine_id)
# Create the base payload and merge with system info # Create the base payload and merge with system info
local base_payload=$(cat <<EOF local base_payload=$(cat <<EOF
{ {
@@ -875,7 +900,8 @@ send_update() {
"hostname": "$HOSTNAME", "hostname": "$HOSTNAME",
"ip": "$IP_ADDRESS", "ip": "$IP_ADDRESS",
"architecture": "$ARCHITECTURE", "architecture": "$ARCHITECTURE",
"agentVersion": "$AGENT_VERSION" "agentVersion": "$AGENT_VERSION",
"machineId": "$machine_id"
} }
EOF EOF
) )

View File

@@ -172,15 +172,6 @@ router.post(
// Generate unique API credentials for this host // Generate unique API credentials for this host
const { apiId, apiKey } = generateApiCredentials(); const { apiId, apiKey } = generateApiCredentials();
// Check if host already exists
const existingHost = await prisma.hosts.findUnique({
where: { friendly_name: friendly_name },
});
if (existingHost) {
return res.status(409).json({ error: "Host already exists" });
}
// If hostGroupId is provided, verify the group exists // If hostGroupId is provided, verify the group exists
if (hostGroupId) { if (hostGroupId) {
const hostGroup = await prisma.host_groups.findUnique({ const hostGroup = await prisma.host_groups.findUnique({
@@ -196,6 +187,7 @@ router.post(
const host = await prisma.hosts.create({ const host = await prisma.hosts.create({
data: { data: {
id: uuidv4(), id: uuidv4(),
machine_id: `pending-${uuidv4()}`, // Temporary placeholder until agent connects with real machine_id
friendly_name: friendly_name, friendly_name: friendly_name,
os_type: "unknown", // Will be updated when agent connects os_type: "unknown", // Will be updated when agent connects
os_version: "unknown", // Will be updated when agent connects os_version: "unknown", // Will be updated when agent connects
@@ -321,6 +313,10 @@ router.post(
.optional() .optional()
.isArray() .isArray()
.withMessage("Load average must be an array"), .withMessage("Load average must be an array"),
body("machineId")
.optional()
.isString()
.withMessage("Machine ID must be a string"),
], ],
async (req, res) => { async (req, res) => {
try { try {
@@ -338,6 +334,11 @@ router.post(
updated_at: new Date(), updated_at: new Date(),
}; };
// Update machine_id if provided and current one is a placeholder
if (req.body.machineId && host.machine_id.startsWith("pending-")) {
updateData.machine_id = req.body.machineId;
}
// Basic system info // Basic system info
if (req.body.osType) updateData.os_type = req.body.osType; if (req.body.osType) updateData.os_type = req.body.osType;
if (req.body.osVersion) updateData.os_version = req.body.osVersion; if (req.body.osVersion) updateData.os_version = req.body.osVersion;

View File

@@ -466,11 +466,23 @@ const HostDetail = () => {
{/* Network Information */} {/* Network Information */}
{activeTab === "network" && {activeTab === "network" &&
(host.gateway_ip || (host.ip ||
host.gateway_ip ||
host.dns_servers || host.dns_servers ||
host.network_interfaces) && ( host.network_interfaces) && (
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{host.ip && (
<div>
<p className="text-xs text-secondary-500 dark:text-secondary-300">
IP Address
</p>
<p className="font-medium text-secondary-900 dark:text-white font-mono text-sm">
{host.ip}
</p>
</div>
)}
{host.gateway_ip && ( {host.gateway_ip && (
<div> <div>
<p className="text-xs text-secondary-500 dark:text-secondary-300"> <p className="text-xs text-secondary-500 dark:text-secondary-300">
@@ -802,6 +814,7 @@ const HostDetail = () => {
{activeTab === "network" && {activeTab === "network" &&
!( !(
host.ip ||
host.gateway_ip || host.gateway_ip ||
host.dns_servers || host.dns_servers ||
host.network_interfaces host.network_interfaces