feat: Implement machine_id based host identification

- Add machine_id field to hosts schema (unique, indexed)
- Remove unique constraint from friendly_name (allow duplicate hostnames)
- Agent installer now generates/reads persistent machine_id
- Proxmox script retrieves machine_id from LXC containers
- Backend will check machine_id instead of hostname for duplicates

This allows multiple hosts with same hostname to coexist in PatchMon
This commit is contained in:
Muhammad Ibrahim
2025-10-04 09:02:56 +01:00
parent f3db16d6d0
commit 811930d1e2
3 changed files with 57 additions and 2 deletions

View File

@@ -109,14 +109,32 @@ cleanup_old_files() {
# Run cleanup at start
cleanup_old_files
# Generate or retrieve machine ID
get_machine_id() {
# Try multiple sources 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 info (less ideal but works)
echo "patchmon-$(cat /sys/class/dmi/id/product_uuid 2>/dev/null || cat /proc/sys/kernel/random/uuid)"
fi
}
# Parse arguments from environment (passed via HTTP headers)
if [[ -z "$PATCHMON_URL" ]] || [[ -z "$API_ID" ]] || [[ -z "$API_KEY" ]]; then
error "Missing required parameters. This script should be called via the PatchMon web interface."
fi
# Get unique machine ID for this host
MACHINE_ID=$(get_machine_id)
export MACHINE_ID
info "🚀 Starting PatchMon Agent Installation..."
info "📋 Server: $PATCHMON_URL"
info "🔑 API ID: ${API_ID:0:16}..."
info "🆔 Machine ID: ${MACHINE_ID:0:16}..."
# Display diagnostic information
echo ""
@@ -261,6 +279,33 @@ if [[ -f "/var/log/patchmon-agent.log" ]]; then
fi
# Step 4: Test the configuration
# Check if this machine is already enrolled
info "🔍 Checking if machine is already enrolled..."
existing_check=$(curl $CURL_FLAGS -s -X POST \
-H "X-API-ID: $API_ID" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"machine_id\": \"$MACHINE_ID\"}" \
"$PATCHMON_URL/api/v1/hosts/check-machine-id" \
-w "\n%{http_code}" 2>&1)
http_code=$(echo "$existing_check" | tail -n 1)
response_body=$(echo "$existing_check" | sed '$d')
if [[ "$http_code" == "200" ]]; then
already_enrolled=$(echo "$response_body" | jq -r '.exists' 2>/dev/null || echo "false")
if [[ "$already_enrolled" == "true" ]]; then
warning "⚠️ This machine is already enrolled in PatchMon"
info "Machine ID: $MACHINE_ID"
info "Existing host: $(echo "$response_body" | jq -r '.host.friendly_name' 2>/dev/null)"
info ""
info "The agent will be reinstalled/updated with existing credentials."
echo ""
else
success "✅ Machine not yet enrolled - proceeding with installation"
fi
fi
info "🧪 Testing API credentials and connectivity..."
if /usr/local/bin/patchmon-agent.sh test; then
success "✅ TEST: API credentials are valid and server is reachable"

View File

@@ -4,7 +4,7 @@ set -eo pipefail # Exit on error, pipe failures (removed -u as we handle unset
# Trap to catch errors only (not normal exits)
trap 'echo "[ERROR] Script failed at line $LINENO with exit code $?"' ERR
SCRIPT_VERSION="1.3.0"
SCRIPT_VERSION="2.0.0"
echo "[DEBUG] Script Version: $SCRIPT_VERSION ($(date +%Y-%m-%d\ %H:%M:%S))"
# =============================================================================
@@ -151,12 +151,16 @@ while IFS= read -r line; do
hostname=$(timeout 5 pct exec "$vmid" -- hostname 2>/dev/null </dev/null || echo "$name")
ip_address=$(timeout 5 pct exec "$vmid" -- hostname -I 2>/dev/null </dev/null | awk '{print $1}' || echo "unknown")
os_info=$(timeout 5 pct exec "$vmid" -- cat /etc/os-release 2>/dev/null </dev/null | grep "^PRETTY_NAME=" | cut -d'"' -f2 || echo "unknown")
# Get machine ID from container
machine_id=$(timeout 5 pct exec "$vmid" -- bash -c "cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null || echo 'proxmox-lxc-$vmid-'$(cat /proc/sys/kernel/random/uuid)" </dev/null 2>/dev/null || echo "proxmox-lxc-$vmid-unknown")
friendly_name="${HOST_PREFIX}${hostname}"
info " Hostname: $hostname"
info " IP Address: $ip_address"
info " OS: $os_info"
info " Machine ID: ${machine_id:0:16}..."
if [[ "$DRY_RUN" == "true" ]]; then
info " [DRY RUN] Would enroll: $friendly_name"
@@ -174,6 +178,7 @@ while IFS= read -r line; do
-H "Content-Type: application/json" \
-d "{
\"friendly_name\": \"$friendly_name\",
\"machine_id\": \"$machine_id\",
\"metadata\": {
\"vmid\": \"$vmid\",
\"proxmox_node\": \"$(hostname)\",

View File

@@ -60,7 +60,8 @@ model host_repositories {
model hosts {
id String @id
friendly_name String @unique
machine_id String @unique
friendly_name String
ip String?
os_type String
os_version String
@@ -92,6 +93,10 @@ model hosts {
host_repositories host_repositories[]
host_groups host_groups? @relation(fields: [host_group_id], references: [id])
update_history update_history[]
@@index([machine_id])
@@index([friendly_name])
@@index([hostname])
}
model packages {