A Python Code to get the Server Instance ID for Remote Management

CK CK 3788 
Created at
Updated at  
366 0 0

I wanted to have a logic to get the unique server instance ID in Python, and below is what I use at the moment.

 

Overall Goal and Format Logic

1. Deterministic and Stable ID: The primary goal is to create an instance_id that is consistent for a given machine across restarts and, as much as possible, unique to that specific machine. This is crucial for logging, monitoring, and identifying specific instances of the application.
2. Format -:

  • Readability & Context: The os_name prefix provides immediate context about the environment (e.g., linux-amazonlinux2023, mac-sequoia). This is more user-friendly than just a raw UUID.
  • Uniqueness: The machine_uuid portion is intended to provide the unique identifier for the machine itself.

A Python Code to get the Server Instance ID for Remote Management

 

_get_os_name() Logic (Determining the OS part)

This function focuses on providing a detailed, human-readable identifier for the operating system.

1. platform.system().lower():

  • Logic: Uses Python's standard platform module to get the generic OS name (e.g., "Linux", "Darwin"). Converts it to lowercase for consistency.
  • Purpose: This is the initial broad categorization of the OS.

2. OS-Specific Branching:

  • Logic: Checks if the system is "linux" or "darwin" (macOS).
  • Purpose: Different operating systems have different ways of identifying their specific distribution or version names. This branching allows for specialized logic for each.
  • _get_linux_distro() (for Linux systems):
    • Core Logic: Parses /etc/os-release, a standard file on most modern Linux distributions (especially systemd-based ones) that contains detailed OS information.
    • Robust File Access: Checks os_release.exists() to prevent errors if the file isn't present.
    • Key-Value Parsing: Reads the file line by line, splitting at = to extract key=value pairs. It strips whitespace and quotes (.strip().strip('"')) for clean data.
    • Prioritization: Looks for ID (e.g., "amazonlinux", "centos") and VERSION_ID (e.g., "2023", "7.0").
    • Combination: Prefers to combine ID and VERSION_ID (e.g., amazonlinux2023, centos7.0) for a more specific identifier. If VERSION_ID is missing, it falls back to just ID.
    • Fallback: Returns "unknown" if /etc/os-release doesn't exist or parsing fails to yield useful IDs.
    • Purpose: To get a more granular and stable Linux distribution identifier than just "linux".
  • _get_macos_name() (for macOS systems):
    • Core Logic: Maps the macOS major version number to its marketing name (e.g., 15 -> "sequoia").
    • Version Extraction: Uses platform.mac_ver()[0] to get the version string (e.g., "15.0.0").
    • Major Version Parsing: Extracts the first component as the major version (int(ver.split(".")[0])). Includes try-except for ValueError or IndexError to handle malformed version strings gracefully.
    • Static Mapping: Uses the _MACOS_NAMES dictionary to look up the marketing name.
    • Fallback: If a major version isn't in the predefined map, it generates a generic name like "macos16". Returns "unknown" if the version cannot be determined.
    • Purpose: To provide a more descriptive and user-friendly macOS name (like "sequoia") instead of just a version number (like "darwin" or "15").

 

_get_machine_uuid() Logic (Determining the Machine UUID part)

This is the most complex part, implementing a prioritized fallback chain to find the most stable and reliable machine identifier, as described in the docstring's “Resolution order.”

1. OS-Specific Prioritization:

  • Logic: It first checks the OS (platform.system().lower()) and then attempts OS-specific methods in a defined order.
  • Purpose: Different OSes provide different best-practice ways to identify a machine.

2. Linux Specific UUID Sources (Prioritized Order):

  • _read_machine_id_file() (Highest Priority on Linux):
    • Target: /etc/machine-id.
    • Logic: Checks if the file exists. If so, it reads the 32-character hexadecimal string, validates its length (must be at least 32), and formats it into a standard UUID string (e.g., xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
    • Robustness: Handles OSError if the file cannot be read.
    • Rationale: This is the most stable and widely available machine ID on modern Linux systems (systemd-based). It's designed to be stable across reboots.
  • _read_dmi_product_uuid() (Second Priority on Linux):
    • Target: /sys/class/dmi/id/product_uuid.
    • Logic: Checks for existence and reads the DMI (Desktop Management Interface) product UUID. This is typically a UUID embedded in the system firmware for bare-metal machines or provided by the hypervisor for VMs.
    • Robustness: Handles PermissionError and OSError as this file often requires root privileges to read.
    • Rationale: A good fallback for bare-metal servers and some VMs, but can be less accessible (permissions) or sometimes not present.

3. macOS Specific UUID Sources (Prioritized Order):

  • _read_macos_platform_uuid() (Highest Priority on macOS):
    • Target: IOPlatformUUID obtained via the ioreg command.
    • Logic: Executes ioreg -rd1 -c IOPlatformExpertDevice as a subprocess. This command queries the I/O Registry for hardware information.
    • Output Parsing: Scans the command's output for the line containing "IOPlatformUUID" and then extracts the UUID string from within the quotes.
    • Robustness: Catches subprocess.TimeoutExpired, FileNotFoundError, and OSError to handle issues with command execution.
    • Rationale: This is the standard, stable way to get a unique hardware identifier on macOS.

4. Persistent UUID File Logic (Universal Fallback, Crucial for Containers):

  • _read_or_create_persistent_uuid(path):
    • Logic: This function is a critical fallback, especially for containerized environments (like Docker) where host-level /etc/machine-id or DMI data might not be available or mounted.
    • "Read-then-Create" Pattern:
      • Read First: If the path (e.g., data/instance-id) exists, it attempts to read the UUID from it.
      • Validation: It tries to parse the read value as a uuid.UUID. If the file contains an invalid UUID, it logs a warning and proceeds to generate a new one, ensuring data integrity.
      • Return Existing: If a valid UUID is read, it's returned, ensuring stability across restarts.
      • Create New: If the file doesn't exist or contains an invalid UUID:
        • Generates a fresh random UUID using uuid.uuid4().
        • Directory Creation: Ensures the parent directory (path.parent) exists using mkdir(parents=True, exist_ok=True) before attempting to write the file. This makes the function robust against missing directories.
        • Writes the newly generated UUID to the file.
        • Logs an informational message about the creation.
    • Robustness: Catches OSError during file operations (read/write/mkdir) and logs a warning, returning None if the persistent file cannot be used.
    • Purpose: Guarantees a stable instance_id for stateless (from the host perspective) environments like Docker containers, by persisting a randomly generated UUID within the container's data volume (if mounted and writable).

5. uuid.getnode() (Last-Resort Fallback):

  • Logic: If all other methods fail to yield a UUID (no system files, no DMI, no ioreg, and no persistent file or writable data directory), this function uses Python's uuid.getnode(). This typically derives a UUID from the MAC address of a network interface.
  • Warning Log: A logger.warning is explicitly included.
  • Rationale:
    • Unstable in Containers: As noted in the docstring, this is highly unstable in Docker because veth (virtual Ethernet) MAC addresses are often regenerated on every docker run, leading to a different instance_id each time, defeating determinism.
    • *Provides something: Despite its instability in some contexts, it always provides a* UUID, preventing the program from crashing if no other identifier can be found. It's a "better than nothing" solution, but with a clear indication of its limitations.

 

generate_instance_id() (Public API Logic)

1. Orchestration: This is the main entry point for the module.
2. Composition: It simply calls _get_os_name() and _get_machine_uuid() to get the two components.
3. Formatting: It then combines them using an f-string: f"{_get_os_name()}-{_get_machine_uuid(persistent_path).lower()}".
4. Lowercasing UUID: The .lower() call on the machine_uuid ensures a consistent case for the UUID part, which is generally a good practice for identifiers.
5. Configurable Persistent Path: persistent_path: Path | None = DEFAULT_PERSISTENT_PATH allows the caller to either use the default path (data/instance-id), specify a different path, or explicitly disable persistent storage by passing None.

 

In summary, the code demonstrates a robust and well-thought-out strategy for generating a deterministic instance ID by:

  • Prioritizing reliable, system-level identifiers.
  • Implementing OS-specific methods.
  • Providing a critical persistent file fallback for containerized environments.
  • Including a last-resort fallback with a clear warning about its limitations.
  • Structuring the ID for both uniqueness and human readability.
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  instance_id.py
#  Author : Chun Kang <ck@ckii.com>
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

"""Auto-generate a deterministic instance_id from OS identity.

Format: ``<os_name>-<machine_uuid>``

Examples::

    linux-amazonlinux2023-abcd1234-5678-...
    linux-centos7.0-abcd1234-5678-...
    mac-sequoia-ABCD1234-5678-...

Resolution order for the machine UUID portion:

1. ``/etc/machine-id`` (Linux, systemd) — stable across reboots.
2. ``/sys/class/dmi/id/product_uuid`` (Linux bare-metal / VMs, usually root-only).
3. ``IOPlatformUUID`` via ``ioreg`` (macOS).
4. A persistent UUID file (default ``data/instance-id``). Created with a fresh
   random UUID on first miss, then read back on every subsequent start. This is
   the path that keeps container IDs stable across restarts when no host
   machine-id is available.
5. ``uuid.getnode()`` as a last-resort, logged-as-warning fallback — this
   produces the ``00000000-0000-0000-0000-<mac>`` pattern, which is unstable
   in Docker because veth MACs change on every ``docker run``.
"""

from __future__ import annotations

import logging
import platform
import subprocess
import uuid
from pathlib import Path

logger = logging.getLogger(__name__)

_MACOS_NAMES: dict[int, str] = {
    11: "bigsur",
    12: "monterey",
    13: "ventura",
    14: "sonoma",
    15: "sequoia",
    16: "tahoe",
}

DEFAULT_PERSISTENT_PATH = Path("data/instance-id")


def _get_os_name() -> str:
    """Return a short OS identifier like ``linux-amazonlinux2023`` or ``mac-sequoia``."""
    system = platform.system().lower()

    if system == "linux":
        return f"linux-{_get_linux_distro()}"
    if system == "darwin":
        return f"mac-{_get_macos_name()}"
    return system


def _get_linux_distro() -> str:
    """Parse /etc/os-release for distro ID and version."""
    os_release = Path("/etc/os-release")
    if not os_release.exists():
        return "unknown"

    info: dict[str, str] = {}
    for line in os_release.read_text().splitlines():
        if "=" in line:
            key, _, val = line.partition("=")
            info[key.strip()] = val.strip().strip('"')

    distro_id = info.get("ID", "unknown")
    version_id = info.get("VERSION_ID", "")

    if version_id:
        return f"{distro_id}{version_id}"
    return distro_id


def _get_macos_name() -> str:
    """Map macOS version to its marketing name."""
    ver = platform.mac_ver()[0]
    if not ver:
        return "unknown"
    try:
        major = int(ver.split(".")[0])
    except (ValueError, IndexError):
        return "unknown"
    return _MACOS_NAMES.get(major, f"macos{major}")


def _read_machine_id_file() -> str | None:
    """Read /etc/machine-id and format as UUID. Returns None if unusable."""
    machine_id_path = Path("/etc/machine-id")
    if not machine_id_path.exists():
        return None
    try:
        hex_id = machine_id_path.read_text().strip()
    except OSError:
        return None
    if len(hex_id) < 32:
        return None
    return f"{hex_id[:8]}-{hex_id[8:12]}-{hex_id[12:16]}-{hex_id[16:20]}-{hex_id[20:32]}"


def _read_dmi_product_uuid() -> str | None:
    """Read /sys/class/dmi/id/product_uuid (Linux bare-metal/VM). Usually root-only."""
    dmi_path = Path("/sys/class/dmi/id/product_uuid")
    if not dmi_path.exists():
        return None
    try:
        value = dmi_path.read_text().strip().lower()
    except (PermissionError, OSError):
        return None
    return value or None


def _read_macos_platform_uuid() -> str | None:
    """Call ioreg to retrieve IOPlatformUUID on macOS."""
    try:
        result = subprocess.run(
            ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
            capture_output=True, text=True, timeout=5, check=False,
        )
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        return None
    for line in result.stdout.splitlines():
        if "IOPlatformUUID" in line:
            parts = line.split('"')
            if len(parts) >= 4:
                return parts[3]
    return None


def _read_or_create_persistent_uuid(path: Path) -> str | None:
    """Read a random UUID from ``path``, creating it on first call.

    Returns None only if both read and create fail (e.g. unwritable directory).
    """
    try:
        if path.exists():
            value = path.read_text().strip()
            if value:
                try:
                    return str(uuid.UUID(value))
                except ValueError:
                    logger.warning("invalid UUID in %s, regenerating", path)
        new_uuid = str(uuid.uuid4())
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(new_uuid)
        logger.info("generated persistent instance_id UUID at %s", path)
        return new_uuid
    except OSError as exc:
        logger.warning("cannot use persistent instance_id file %s: %s", path, exc)
        return None


def _get_machine_uuid(persistent_path: Path | None = None) -> str:
    """Resolve the machine UUID by walking the fallback chain."""
    system = platform.system().lower()

    if system == "linux":
        value = _read_machine_id_file()
        if value:
            return value
        value = _read_dmi_product_uuid()
        if value:
            return value

    if system == "darwin":
        value = _read_macos_platform_uuid()
        if value:
            return value

    if persistent_path is not None:
        value = _read_or_create_persistent_uuid(persistent_path)
        if value:
            return value

    logger.warning(
        "falling back to MAC-based instance UUID; this is unstable in containers — "
        "mount /etc/machine-id, set ODX_INSTANCE_ID, or ensure the data volume is writable"
    )
    return str(uuid.UUID(int=uuid.getnode()))


def generate_instance_id(persistent_path: Path | None = DEFAULT_PERSISTENT_PATH) -> str:
    """Generate a deterministic instance_id from OS identity.

    Returns ``<os_name>-<machine_uuid>``,
    e.g. ``mac-sequoia-ABCD1234-5678-90AB-CDEF-1234567890AB``.
    """
    return f"{_get_os_name()}-{_get_machine_uuid(persistent_path).lower()}"
Tags Container ID Deterministic ID Instance ID Machine UUID Operating System Server Management Unique Instance ID Unique Server ID Facebook X
Comments 0
Similar posts
  1. Dynamic DNS Made Easy: Building a Python-Based Solution
    796
  2. Uvicorn, Gunicorn: Proving Your Production Prowess
    401
  1. What is RedisVL?
    114
  2. What “GPU Clustering” Really Means (And How to Code for It)
    214
  3. How to Build a Python Chatbot That Writes and Runs Code Using Microsoft AutoGen (With Sample Code)
    244
  4. Beyond Prompt Engineering: Why Loop Engineering is the Future of Autonomous AI
    310
  5. From Local Python Web to Standalone
    2,056
  6. Demystifying FAISS: Your Guide to ANN(Approximate Nearest Neighbor) Search
    326
  7. TensorRT for LLMs: Speed, Scale, and Why Startups Choose It Over TensorFlow
    466
  8. Python ML Technologies used for the target Segmentation
    380
  9. Managing WHITE_LIST in FastAPI to Improve the Server Security
    354
  10. A common async example
    989
  11. Milvus and LLM Integration - Very Smart Approach!
    304
  12. Automated API Documentation with PyCharm
    896
  13. How to install python 3.9 alongside python 3.8 on CentOS 7?
    719
  14. How to run Python 3.9 on Mac?
    656
  15. Python Quizs
    502
  16. Automated OpenAPI documentation for a Python Flask API
    1,125
  17. Automated API documentation for a Django REST Framework (DRF) project using drf-yasg (Yet Another Swagger Generator)
    1,033
  18. Uploading a file to a Google Drive Folder programmatically with Python
    3,309
  19. Garbage Collection when your system does not have enough memory in your Python App
    1,021
Recently updated
  1. Google OAuth Token Expiration: Causes and Solutions
    29
  2. Why 100% SEO Guarantees Better User Engagement and How to Achieve It in Your Service
    55
  3. Why SPF and DKIM Are Essential for Linux Email Servers?
    49
  4. How do I create animated background for Google Meet?
    22,876
  5. MaxMind GeoLite2 vs. DB-IP Lite vs. ip-api.com: Choosing the Right Free IP Geolocation Solution
    271
  6. Meet Valkey: Why Redis Changed Its License, Why Valkey is Better, and How to Use It as a Drop-in Replacement
    103
  7. How Rogue Web Scrapers Are Stealing My Content and Destroying My Server
    168
  8. Array of arguments passed to script in PHP
    97
  9. Invalid Auction Storms in Ad Tech
    106
  10. Building MRC-Compliant Instrumentation in Ad Tech: Implementation Steps and Key Challenges
    207
  11. The Two Clocks of Ad Server Architecture
    77
  12. Frequency Cap Architecture Overview
    526
  13. Collecting Proof-of-Play from 100,000 Screens
    152
  14. Vector Search in Relational Databases (RDBs): Trends & Usage Environments
    302
  15. Designing a Skill Registry Architecture: Minimize Token Usage and Maximize AI Performance
    246
  16. TPU & GPU Cluster Management Tools
    257
  17. The Concept of Scaffolding in AI Agents
    244
  18. Beyond Static Pipelines: Architecting LLM Multi-Agent Systems for Autonomous Workflow Orchestration
    596
  19. AI-DLC vs. AI-SDLC: Model-Centric vs. System-Centric AI Development
    267
  20. Fine-Tuning Open-Weight LLMs: Training, Licensing, and Deploying Custom Models with Ollama
    353
  21. Inside the Next-Generation Streaming Architecture: Ultra-Low Latency, SGAI, and AI Codecs
    206
  22. Machine Learning: Training vs. Ranking
    229
  23. The Continuous Learning Machine: Designing a Loop That Actually Turns
    228
  24. The Next-Gen Low-Latency Standards: LL-HLS & MoQ
    202
  25. Designing a Data Curation System Around a Vector Database
    230
  26. Extracting Context from Live Video: The Architecture Got Simpler When We Stopped Building Features
    263
  27. The New Ad Stack Behind Live Sports Streaming: Inside SGAI, EABN, and the Standards Race to Fix Live Monetization
    671
  28. Designing MLOps Architectures to Handle Seasonal Data Drift in EV Fleet Models
    200
  29. From 15 to 40+ FPS: Accelerating PyTorch Object Detection on Automotive ECUs
    232
  30. Understanding Data Lineage
    271