machineid

package module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 14, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

machineid

main branch GitHub go.mod Go version Go Reference Go Report Card license Release

A zero-dependency Go library that generates unique, deterministic machine identifiers from hardware characteristics. IDs are stable across reboots, sensitive to hardware changes, and ideal for software licensing, device fingerprinting, and telemetry correlation.

Features

  • Zero Dependencies — built entirely on the Go standard library
  • Cross-Platform — macOS, Linux, and Windows
  • Configurable — choose which hardware signals to include (CPU, Motherboard, System UUID, MAC, Disk)
  • Power-of-2 Output — 32, 64, 128, or 256 hex characters
  • SHA-256 Hashing — cryptographically secure, no collisions in practice
  • Salt Support — application-specific IDs on the same machine
  • VM Friendly — preset for virtual environments (CPU + UUID)
  • Thread-Safe — safe for concurrent use after configuration
  • Diagnostic API — inspect which components succeeded or failed
  • Optional Logging*slog.Logger support for observability with zero overhead when disabled
  • Structured Errors — sentinel errors and typed errors for programmatic handling via errors.Is / errors.As
  • Testable — dependency-injectable command executor

Installation

Library

Add the module to your Go project:

go get github.com/slashdevops/machineid

Requires Go 1.25+. No external dependencies.

CLI Tool
Using go install
go install github.com/slashdevops/machineid/cmd/machineid@latest

Make sure ~/go/bin is in your PATH:

mkdir -p ~/go/bin

# bash
cat >> ~/.bash_profile <<EOL
export PATH=\$PATH:~/go/bin
EOL

source ~/.bash_profile

# zsh
cat >> ~/.zshrc <<EOL
export PATH=\$PATH:~/go/bin
EOL

source ~/.zshrc
Installing a Precompiled Binary

Precompiled binaries for macOS, Linux, and Windows are available on the releases page.

You can download them with the GitHub CLI (gh):

brew install gh   # if not already installed

Then fetch and install the binary:

export TOOL_NAME="machineid"
export GIT_ORG="slashdevops"
export GIT_REPO="machineid"
export OS=$(uname -s | tr '[:upper:]' '[:lower:]')
export OS_ARCH=$(uname -m | tr '[:upper:]' '[:lower:]')
export ASSETS_NAME=$(gh release view --repo ${GIT_ORG}/${GIT_REPO} --json assets -q "[.assets[] | select(.name | contains(\"${TOOL_NAME}\") and contains(\"${OS}\") and contains(\"${OS_ARCH}\"))] | sort_by(.createdAt) | last.name")

gh release download --repo $GIT_ORG/$GIT_REPO --pattern $ASSETS_NAME
unzip $ASSETS_NAME
rm $ASSETS_NAME

mv $TOOL_NAME ~/go/bin/$TOOL_NAME
~/go/bin/$TOOL_NAME -version
Building from Source

Clone the repository and build with version metadata via the provided Makefile:

git clone https://github.com/slashdevops/machineid.git
cd machineid
make build
./build/machineid -version

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/slashdevops/machineid"
)

func main() {
    ctx := context.Background()
    id, err := machineid.New().
        WithCPU().
        WithSystemUUID().
        ID(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(id)
    // Output: 64-character hex string (e.g. b5c42832542981af…)
}

Usage

Selecting Hardware Components

Enable one or more hardware sources via the With* methods:

ctx := context.Background()
provider := machineid.New().
    WithCPU().            // processor ID and feature flags
    WithMotherboard().    // motherboard serial number
    WithSystemUUID().     // BIOS/UEFI system UUID
    WithMAC().            // physical network interface MAC addresses (default filter)

    WithDisk()            // internal disk serial numbers

id, err := provider.ID(ctx)
MAC Address Filtering

Control which network interfaces are included in the machine ID using MACFilter:

ctx := context.Background()

// Physical interfaces only (default, most stable for bare-metal)
id, _ := machineid.New().WithCPU().WithMAC().ID(ctx)

// All interfaces including virtual (VPN, Docker, bridges)
id, _ = machineid.New().WithCPU().WithMAC(machineid.MACFilterAll).ID(ctx)

// Only virtual interfaces (useful for container-specific fingerprinting)
id, _ = machineid.New().WithCPU().WithMAC(machineid.MACFilterVirtual).ID(ctx)
Filter Interfaces Included Best For
MACFilterPhysical en0, eth0, wlan0 (default) Bare-metal stability
MACFilterAll Physical + virtual (docker0, utun, bridge, etc.) Maximum uniqueness
MACFilterVirtual docker0, utun, bridge0, veth, vmnet, etc. Container fingerprinting
Output Formats

All formats produce pure hexadecimal strings without dashes:

ctx := context.Background()

// 32 characters (2^5) — compact
id, _ := machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format32).ID(ctx)

// 64 characters (2^6) — default, full SHA-256
id, _ = machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format64).ID(ctx)

// 128 characters (2^7) — extended
id, _ = machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format128).ID(ctx)

// 256 characters (2^8) — maximum
id, _ = machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format256).ID(ctx)
Format Length Bits Collision Probability (1 B IDs) Use Case
Format32 32 128 ~1.47 × 10⁻²¹ Compact identifiers
Format64 64 256 ~4.32 × 10⁻⁶⁰ Default, recommended
Format128 128 512 Virtually zero Extended security
Format256 256 1024 Astronomically low Maximum security
Custom Salt

A salt ensures the same machine produces different IDs for different applications:

ctx := context.Background()
id, _ := machineid.New().
    WithCPU().
    WithSystemUUID().
    WithSalt("my-app-v1").
    ID(ctx)
VM-Friendly Mode

For virtual machines where disk serials and MACs may be unstable:

ctx := context.Background()
id, _ := machineid.New().
    VMFriendly().  // CPU + System UUID only
    WithSalt("my-app").
    ID(ctx)
Validation

Check whether a stored ID still matches the current hardware:

ctx := context.Background()
provider := machineid.New().WithCPU().WithSystemUUID()
valid, err := provider.Validate(ctx, storedID)
Diagnostics

Inspect which hardware components were successfully collected:

ctx := context.Background()
provider := machineid.New().
    WithCPU().
    WithSystemUUID().
    WithDisk()

id, _ := provider.ID(ctx)

diag := provider.Diagnostics()
fmt.Println("Collected:", diag.Collected)  // e.g. [cpu uuid]
fmt.Println("Errors:", diag.Errors)        // e.g. map[disk: no internal disk identifiers found]
Logging

Enable optional logging with any *slog.Logger for observability. When no logger is set (the default), there is zero overhead:

import (
    "log/slog"
    "os"
)

ctx := context.Background()
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

id, err := machineid.New().
    WithCPU().
    WithSystemUUID().
    WithLogger(logger).
    ID(ctx)
Log Level What's Logged
Info Component collected, fallback triggered, ID generation lifecycle
Warn Component failed or returned empty value
Debug Command execution details (name, args, duration), raw hardware values

The logger is compatible with slog.Default() which bridges to the standard log package:

// Use the standard library default logger
provider.WithLogger(slog.Default())
Error Handling

The package provides sentinel errors for errors.Is and typed errors for errors.As:

id, err := provider.ID(ctx)
if errors.Is(err, machineid.ErrNoIdentifiers) {
    // No hardware identifiers were collected
}
Sentinel Errors
Error Meaning
ErrNoIdentifiers No hardware identifiers collected with current config
ErrEmptyValue A component returned an empty value
ErrNoValues A multi-value component (MAC, disk) returned no values
ErrNotFound A value was not found in command output or system files
ErrOEMPlaceholder A value matches a BIOS/UEFI placeholder ("To be filled...")
ErrAllMethodsFailed All collection methods for a component were exhausted
Typed Errors

Use errors.As to extract structured context from errors:

// Check if a system command failed
var cmdErr *machineid.CommandError
if errors.As(err, &cmdErr) {
    fmt.Println("command:", cmdErr.Command) // e.g. "sysctl", "ioreg", "wmic"
}

// Check if output parsing failed
var parseErr *machineid.ParseError
if errors.As(err, &parseErr) {
    fmt.Println("source:", parseErr.Source) // e.g. "system_profiler JSON"
}

// Inspect diagnostic errors per component
diag := provider.Diagnostics()
var compErr *machineid.ComponentError
if errors.As(diag.Errors["cpu"], &compErr) {
    fmt.Println("component:", compErr.Component)
    fmt.Println("cause:", compErr.Err)
}

CLI Tool

A ready-to-use command-line tool is included.

See the Installation section above for all ways to install the CLI.

Examples
# Generate an ID from CPU + UUID (default 64 chars)
machineid -cpu -uuid

# All hardware sources, compact 32-char format
machineid -all -format 32

# VM-friendly with custom salt
machineid -vm -salt "my-app"

# JSON output with diagnostics
machineid -cpu -uuid -json -diagnostics

# Validate a previously stored ID
machineid -cpu -uuid -validate "b5c42832542981af58c9dc3bc241219e780ff7d276cfad05fac222846edb84f7"

# Info-level logging (fallbacks, lifecycle events)
machineid -cpu -uuid -verbose

# Include only physical MACs (default)
machineid -mac -mac-filter physical

# Include all MACs (physical + virtual)
machineid -all -mac-filter all

# Debug-level logging (command details, raw values, timing)
machineid -all -debug

# Version information
machineid -version
machineid -version.long
All Flags
Flag Description
-cpu Include CPU identifier
-motherboard Include motherboard serial number
-uuid Include system UUID
-mac Include network MAC addresses
-mac-filter F MAC filter: physical (default), all, or virtual
-disk Include disk serial numbers
-all Include all hardware identifiers
-vm VM-friendly mode (CPU + UUID only)
-format N Output length: 32, 64 (default), 128, or 256
-salt STRING Custom salt for application-specific IDs
-validate ID Validate an ID against the current machine
-diagnostics Show collected/failed components
-json Output as JSON
-verbose Enable info-level logging to stderr (fallbacks, lifecycle)
-debug Enable debug-level logging to stderr (commands, values, timing)
-version Show version information
-version.long Show detailed version information

How It Works

  1. Collect — gather hardware identifiers based on the provider configuration
  2. Sort — sort identifiers alphabetically for deterministic ordering
  3. Hash — apply SHA-256 to the concatenated identifiers (with optional salt)
  4. Format — truncate or extend the hash to the selected power-of-2 length
Platform Details
Platform CPU UUID Motherboard Disk MAC
macOS sysctl, system_profiler system_profiler, ioreg system_profiler, ioreg system_profiler net.Interfaces
Linux /proc/cpuinfo /sys/class/dmi/id, /etc/machine-id /sys/class/dmi/id lsblk, /sys/block net.Interfaces
Windows wmic, PowerShell wmic, PowerShell wmic, PowerShell wmic, PowerShell net.Interfaces

Each source has fallback methods for resilience across OS versions and configurations.

Testing

The library supports dependency injection for deterministic testing without real system commands:

type mockExecutor struct {
    outputs map[string]string
}

func (m *mockExecutor) Execute(ctx context.Context, name string, args ...string) (string, error) {
    if output, ok := m.outputs[name]; ok {
        return output, nil
    }
    return "", fmt.Errorf("command not found: %s", name)
}

provider := machineid.New().
    WithExecutor(&mockExecutor{
        outputs: map[string]string{
            "sysctl": "Intel Core i9",
        },
    }).
    WithCPU()

id, err := provider.ID()

Run the test suite:

go test -v -race ./...

Security Considerations

  • SHA-256 is a cryptographically secure one-way hash — hardware details cannot be recovered from an ID
  • Sorting ensures consistent output regardless of collection order
  • Salt support prevents cross-application ID reuse
  • No personally identifiable information (PII) is exposed in the output

Best Practices

Choosing a Format
Format Recommendation
Format32 Embedded systems or storage-constrained environments
Format64 Recommended for most use cases (default)
Format128 Extra security margin or regulatory requirements
Format256 Maximum security for critical applications
Hardware Identifier Selection
// Minimal (VMs, containers)
id, _ := machineid.New().VMFriendly().ID()

// Balanced (recommended)
id, _ := machineid.New().
    WithCPU().
    WithSystemUUID().
    WithMotherboard().
    ID()

// Maximum (most unique, but sensitive to hardware changes)
id, _ := machineid.New().
    WithCPU().
    WithSystemUUID().
    WithMotherboard().
    WithMAC().
    WithDisk().
    ID()

Troubleshooting

Git Tag Push Error: "push declined due to repository rule violations"

If you encounter this error when trying to push a tag:

! [remote rejected] v0.0.1 -> v0.0.1 (push declined due to repository rule violations)
error: failed to push some refs to 'github.com:slashdevops/machineid.git'

Cause: This happens when you try to create a tag with a version number that is older than existing tags. GitHub repository rules enforce semantic versioning order to prevent version rollback.

Solution: Create a tag with a version number higher than all existing tags.

  1. Check existing tags:

    git tag -l
    
  2. Create the next appropriate version:

    # If the latest tag is v0.0.2, use v0.0.3 or higher
    git tag -a "v0.0.3" -m "Release v0.0.3"
    git push origin v0.0.3
    

For more information about versioning and releases, see CONTRIBUTING.md.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines on how to contribute, including information about versioning, testing, and code style.

License

Apache License 2.0

Documentation

Overview

Package machineid generates unique, deterministic machine identifiers derived from hardware characteristics. The generated IDs are stable across reboots but sensitive to hardware changes, making them suitable for software licensing, device fingerprinting, and telemetry correlation.

Zero Dependencies

This package relies exclusively on the Go standard library and OS-level commands. There are no third-party dependencies.

Overview

A Provider collects hardware signals (CPU, motherboard serial, system UUID, MAC addresses, disk serials), sorts and concatenates them, then produces a SHA-256 based hexadecimal fingerprint. The result length is always a power of two: 32, 64, 128, or 256 characters, controlled by FormatMode.

Quick Start

id, err := machineid.New().
	WithCPU().
	WithSystemUUID().
	ID(ctx)

Configuring Hardware Sources

Enable individual hardware components via the With* methods:

Or use Provider.VMFriendly to select a minimal, virtual-machine-safe subset (CPU + System UUID).

MAC Address Filtering

Provider.WithMAC accepts an optional MACFilter to control which network interfaces contribute to the machine ID:

Examples:

// Physical interfaces only (default, most stable)
provider.WithMAC()

// Include all interfaces
provider.WithMAC(machineid.MACFilterAll)

// Only virtual interfaces (containers, VPNs)
provider.WithMAC(machineid.MACFilterVirtual)

Output Formats

Set the output length with Provider.WithFormat:

  • Format32 — 32 hex characters (128 bits, truncated SHA-256)
  • Format64 — 64 hex characters (256 bits, full SHA-256, default)
  • Format128 — 128 hex characters (512 bits, double SHA-256)
  • Format256 — 256 hex characters (1024 bits, quadruple SHA-256)

All formats produce pure hexadecimal strings without dashes.

Salt

Provider.WithSalt mixes an application-specific string into the hash so that two applications on the same machine produce different IDs:

id, _ := machineid.New().
	WithCPU().
	WithSystemUUID().
	WithSalt("my-app-v1").
	ID(ctx)

Validation

Provider.Validate regenerates the ID and compares it to a previously stored value:

valid, err := provider.Validate(ctx, storedID)

Diagnostics

After calling Provider.ID, call Provider.Diagnostics to inspect which components were collected and which encountered errors:

diag := provider.Diagnostics()
fmt.Println("Collected:", diag.Collected)
fmt.Println("Errors:", diag.Errors)

Logging

Provider.WithLogger accepts a *log/slog.Logger for optional observability. When set, the provider logs component collection results, fallback paths, command execution timing, and errors. A nil logger (the default) disables all logging with zero overhead.

logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
id, err := machineid.New().
	WithCPU().
	WithSystemUUID().
	WithLogger(logger).
	ID(ctx)

Log levels:

  • Info: component collected, fallback triggered, ID generation lifecycle
  • Warn: component failed or returned empty value
  • Debug: command execution details, raw hardware values, timing

Errors

The package provides sentinel errors for programmatic error handling:

Typed errors provide structured context for errors.As:

  • CommandError — a system command execution failed (includes the command name)
  • ParseError — output parsing failed (includes the data source)
  • ComponentError — a hardware component failed (includes the component name)

Errors in [DiagnosticInfo.Errors] are wrapped in ComponentError, so callers can inspect both the component name and the underlying cause:

var compErr *machineid.ComponentError
if errors.As(diag.Errors["cpu"], &compErr) {
	fmt.Println("component:", compErr.Component)
	fmt.Println("cause:", compErr.Err)
}

Thread Safety

A Provider is safe for concurrent use after configuration is complete. The first successful call to Provider.ID freezes the configuration and caches the result; subsequent calls return the cached value.

Testing

Inject a custom CommandExecutor via Provider.WithExecutor to replace real system commands with deterministic test doubles:

provider := machineid.New().
	WithExecutor(myMock).
	WithCPU()

Platform Support

Supported operating systems: macOS (darwin), Linux, and Windows. Each platform uses native tools (system_profiler / ioreg, /sys / lsblk, wmic / PowerShell) to collect hardware data.

Installation

To use machineid as a library in your Go project:

go get github.com/slashdevops/machineid

To install the CLI tool:

go install github.com/slashdevops/machineid/cmd/machineid@latest

Precompiled binaries for macOS, Linux, and Windows are available on the [releases page]: https://github.com/slashdevops/machineid/releases

CLI Tool

A ready-to-use command-line tool is provided in cmd/machineid:

machineid -cpu -uuid
machineid -all -format 32 -json
machineid -vm -salt "my-app" -diagnostics
machineid -mac -mac-filter all
machineid -cpu -uuid -verbose
machineid -all -debug
machineid -version
machineid -version.long
Example (CollisionResistance)

Example_collisionResistance demonstrates the collision resistance of different formats.

package main

import (
	"fmt"
	"math"
)

func main() {
	// Calculate collision probability (simplified)
	format32Bits := 128.0 // 32 hex chars = 128 bits
	format64Bits := 256.0 // 64 hex chars = 256 bits

	// For random IDs, probability of collision after N IDs (birthday paradox):
	// P(collision) ≈ N^2 / (2 * 2^bits)
	// For no collision with 1 billion IDs:
	n := 1e9 // 1 billion IDs

	// Format32 (128 bits)
	collisionProb32 := (n * n) / (2 * math.Pow(2, format32Bits))
	fmt.Printf("Format32 collision probability with 1B IDs: %.2e\n", collisionProb32)

	// Format64 (256 bits) - essentially zero
	collisionProb64 := (n * n) / (2 * math.Pow(2, format64Bits))
	fmt.Printf("Format64 collision probability with 1B IDs: %.2e\n", collisionProb64)

	fmt.Printf("Format64 is more secure: %v\n", collisionProb64 < collisionProb32)

}
Output:

Format32 collision probability with 1B IDs: 1.47e-21
Format64 collision probability with 1B IDs: 4.32e-60
Format64 is more secure: true
Example (PowerOfTwo)

Example_powerOfTwo demonstrates why power-of-2 lengths are beneficial.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	// Format32: 32 hex chars = 128 bits = 2^128 possible values
	id32, _ := machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format32).ID(context.Background())
	fmt.Printf("Format32 (2^5 chars): %d characters\n", len(id32))
	fmt.Printf("Format32 bits: %d (2^%d possible values)\n", len(id32)*4, len(id32)*4)

	// Format64: 64 hex chars = 256 bits = 2^256 possible values (full SHA-256)
	id64, _ := machineid.New().WithCPU().WithSystemUUID().WithFormat(machineid.Format64).ID(context.Background())
	fmt.Printf("Format64 (2^6 chars): %d characters\n", len(id64))
	fmt.Printf("Format64 bits: %d (2^%d possible values)\n", len(id64)*4, len(id64)*4)

}
Output:

Format32 (2^5 chars): 32 characters
Format32 bits: 128 (2^128 possible values)
Format64 (2^6 chars): 64 characters
Format64 bits: 256 (2^256 possible values)

Index

Examples

Constants

View Source
const (
	ComponentCPU         = "cpu"
	ComponentMotherboard = "motherboard"
	ComponentSystemUUID  = "uuid"
	ComponentMAC         = "mac"
	ComponentDisk        = "disk"
	ComponentMachineID   = "machine-id" // Linux systemd machine-id
)

Component names used as keys in DiagnosticInfo.

Variables

View Source
var (
	// ErrNoIdentifiers is returned when no hardware identifiers could be
	// collected with the current configuration.
	ErrNoIdentifiers = errors.New("no hardware identifiers found with current configuration")

	// ErrEmptyValue is returned in [DiagnosticInfo.Errors] when a hardware
	// component returned an empty value.
	ErrEmptyValue = errors.New("empty value returned")

	// ErrNoValues is returned in [DiagnosticInfo.Errors] when a hardware
	// component returned no values.
	ErrNoValues = errors.New("no values found")

	// ErrNotFound is returned when a hardware value is not found in
	// command output or system files.
	ErrNotFound = errors.New("value not found")

	// ErrOEMPlaceholder is returned when a hardware value matches a
	// BIOS/UEFI OEM placeholder such as "To be filled by O.E.M.".
	ErrOEMPlaceholder = errors.New("value is OEM placeholder")

	// ErrAllMethodsFailed is returned when all collection methods for a
	// hardware component have been exhausted without success.
	ErrAllMethodsFailed = errors.New("all collection methods failed")
)

Sentinel errors returned by Provider.ID and recorded in [DiagnosticInfo.Errors].

Functions

This section is empty.

Types

type CommandError

type CommandError struct {
	Command string // command name, e.g. "sysctl", "ioreg", "wmic"
	Err     error  // underlying error from exec
}

CommandError records a failed system command execution. Use errors.As to extract the command name from wrapped errors.

func (*CommandError) Error

func (e *CommandError) Error() string

Error returns a human-readable description of the command failure.

func (*CommandError) Unwrap

func (e *CommandError) Unwrap() error

Unwrap returns the underlying error.

type CommandExecutor

type CommandExecutor interface {
	Execute(ctx context.Context, name string, args ...string) (string, error)
}

CommandExecutor is an interface for executing system commands, allowing for dependency injection and testing.

type ComponentError

type ComponentError struct {
	Component string // component name, e.g. "cpu", "uuid", "disk"
	Err       error  // underlying error
}

ComponentError records a failure while collecting a specific hardware component. These errors appear in [DiagnosticInfo.Errors] and can be inspected with errors.As.

func (*ComponentError) Error

func (e *ComponentError) Error() string

Error returns a human-readable description of the component failure.

func (*ComponentError) Unwrap

func (e *ComponentError) Unwrap() error

Unwrap returns the underlying error.

type DiagnosticInfo

type DiagnosticInfo struct {
	Errors    map[string]error // Component names that failed with their errors
	Collected []string         // Component names that were successfully collected
}

DiagnosticInfo contains information about what was collected during ID generation. Use Provider.Diagnostics to retrieve this information after calling Provider.ID.

type FormatMode

type FormatMode int

FormatMode defines the output format and length of the machine ID.

const (
	// Format64 outputs 64 hex characters (2^6), default SHA-256 output without dashes.
	Format64 FormatMode = iota
	// Format32 outputs 32 hex characters (2^5), truncated SHA-256.
	Format32
	// Format128 outputs 128 hex characters (2^7), double SHA-256.
	Format128
	// Format256 outputs 256 hex characters (2^8), quadruple SHA-256.
	Format256
)

type MACFilter

type MACFilter int

MACFilter controls which network interfaces are included in MAC address collection.

const (
	// MACFilterPhysical includes only physical network interfaces (default).
	// Virtual, VPN, bridge, and container interfaces are excluded.
	MACFilterPhysical MACFilter = iota
	// MACFilterAll includes all non-loopback, up network interfaces (physical and virtual).
	MACFilterAll
	// MACFilterVirtual includes only virtual network interfaces
	// (VPN, bridge, container, and hypervisor interfaces).
	MACFilterVirtual
)

func (MACFilter) String

func (f MACFilter) String() string

String returns the string representation of the MACFilter.

type ParseError

type ParseError struct {
	Source string // data source, e.g. "system_profiler JSON", "wmic output"
	Err    error  // underlying parse error
}

ParseError records a failure while parsing command or system output. Use errors.As to extract the source from wrapped errors.

func (*ParseError) Error

func (e *ParseError) Error() string

Error returns a human-readable description of the parse failure.

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

Unwrap returns the underlying error.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider configures and generates unique machine IDs. After the first call to Provider.ID, the configuration is frozen and the result is cached. Provider methods are safe for concurrent use after configuration is complete.

Example

ExampleProvider demonstrates basic usage of the machineid package. This example shows how to generate a unique machine identifier that is stable across reboots and suitable for licensing.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	// Create a new provider with CPU and System UUID
	// These are typically stable identifiers on most systems
	provider := machineid.New().
		WithCPU().
		WithSystemUUID().
		WithSalt("my-application-v1")

	// Generate the machine ID
	id, err := provider.ID(context.Background())
	if err != nil {
		fmt.Printf("Error generating ID: %v\n", err)
		return
	}

	// The ID is a 64-character hexadecimal string (SHA-256 hash, power of 2)
	fmt.Printf("Machine ID length: %d\n", len(id))
	fmt.Printf("Machine ID is hexadecimal: %v\n", isHexString(id))

	// Validate the ID
	valid, err := provider.Validate(context.Background(), id)
	if err != nil {
		fmt.Printf("Error validating ID: %v\n", err)
		return
	}
	fmt.Printf("ID is valid: %v\n", valid)

}

// isHexString reports whether s is a non-empty string of lowercase hex digits.
func isHexString(s string) bool {
	if len(s) == 0 {
		return false
	}
	for _, c := range s {
		if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
			return false
		}
	}
	return true
}
Output:

Machine ID length: 64
Machine ID is hexadecimal: true
ID is valid: true

func New

func New() *Provider

New creates a new Provider with default settings. The provider uses real system commands by default. Default format is Format64 (64 hex characters, 2^6).

Example

ExampleNew demonstrates the simplest way to generate a machine ID.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	provider := machineid.New().
		WithCPU().
		WithSystemUUID()

	id, err := provider.ID(context.Background())
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}

	fmt.Printf("ID length: %d\n", len(id))
}
Output:

ID length: 64

func (*Provider) Diagnostics

func (p *Provider) Diagnostics() *DiagnosticInfo

Diagnostics returns information about which hardware components were successfully collected and which ones failed during the last call to [ID]. Returns nil if [ID] has not been called yet.

func (*Provider) ID

func (p *Provider) ID(ctx context.Context) (string, error)

ID generates the machine ID based on the configured options. It caches the result, so subsequent calls return the same ID. The configuration is frozen after the first successful call. The provided context controls the timeout and cancellation of any system commands executed during hardware identifier collection. This method is safe for concurrent use.

Example (AllComponents)

ExampleProvider_ID_allComponents shows using every available hardware source.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	provider := machineid.New().
		WithCPU().
		WithMotherboard().
		WithSystemUUID().
		WithMAC().
		WithDisk().
		WithSalt("full-example")

	id, err := provider.ID(context.Background())
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}

	fmt.Printf("Full ID length: %d\n", len(id))
	fmt.Printf("Is hex: %v\n", isAllHex(id))
}

func isAllHex(s string) bool {
	for _, c := range s {
		if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
			return false
		}
	}
	return len(s) > 0
}
Output:

Full ID length: 64
Is hex: true

func (*Provider) VMFriendly

func (p *Provider) VMFriendly() *Provider

VMFriendly configures the provider for virtual machines (CPU + UUID only).

Example

ExampleProvider_VMFriendly demonstrates creating a VM-friendly machine ID. This configuration works well in virtual machine environments where hardware like disk serials and MAC addresses may change frequently.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	provider := machineid.New().
		VMFriendly().
		WithSalt("vm-app")

	id, err := provider.ID(context.Background())
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Generated VM-friendly ID: %v\n", len(id) == 64)
}
Output:

Generated VM-friendly ID: true
Example (Preset)

ExampleProvider_VMFriendly_preset demonstrates the VM-friendly preset.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	id, err := machineid.New().
		VMFriendly().
		ID(context.Background())
	if err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}

	fmt.Printf("VM-friendly ID length: %d\n", len(id))
}
Output:

VM-friendly ID length: 64

func (*Provider) Validate

func (p *Provider) Validate(ctx context.Context, id string) (bool, error)

Validate reports whether the provided ID matches the current machine ID. The provided context is forwarded to Provider.ID if it needs to generate the ID.

Example

ExampleProvider_Validate shows how to check a stored ID against the current machine.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	provider := machineid.New().
		WithCPU().
		WithSystemUUID()

	id, _ := provider.ID(context.Background())

	// Validate the correct ID
	valid, _ := provider.Validate(context.Background(), id)
	fmt.Printf("Correct ID valid: %v\n", valid)

	// Validate an incorrect ID
	valid, _ = provider.Validate(context.Background(), "0000000000000000000000000000000000000000000000000000000000000000")
	fmt.Printf("Wrong ID valid: %v\n", valid)

}
Output:

Correct ID valid: true
Wrong ID valid: false

func (*Provider) WithCPU

func (p *Provider) WithCPU() *Provider

WithCPU includes the CPU identifier in the generation.

func (*Provider) WithDisk

func (p *Provider) WithDisk() *Provider

WithDisk includes disk serial numbers in the generation.

func (*Provider) WithExecutor

func (p *Provider) WithExecutor(executor CommandExecutor) *Provider

WithExecutor sets a custom CommandExecutor, enabling deterministic testing without real system commands.

func (*Provider) WithFormat

func (p *Provider) WithFormat(mode FormatMode) *Provider

WithFormat sets the output format and length. Use Format64 (default), Format32, Format128, or Format256.

Example

ExampleProvider_WithFormat demonstrates the four power-of-two output formats.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	ctx := context.Background()
	base := func(mode machineid.FormatMode) int {
		id, err := machineid.New().
			WithCPU().
			WithSystemUUID().
			WithFormat(mode).
			ID(ctx)
		if err != nil {
			return -1
		}
		return len(id)
	}

	fmt.Printf("Format32:  %d chars\n", base(machineid.Format32))
	fmt.Printf("Format64:  %d chars\n", base(machineid.Format64))
	fmt.Printf("Format128: %d chars\n", base(machineid.Format128))
	fmt.Printf("Format256: %d chars\n", base(machineid.Format256))
}
Output:

Format32:  32 chars
Format64:  64 chars
Format128: 128 chars
Format256: 256 chars

func (*Provider) WithLogger

func (p *Provider) WithLogger(logger *slog.Logger) *Provider

WithLogger sets an optional *slog.Logger for observability. When set, the provider logs component collection, fallback paths, command execution timing, and errors. A nil logger (the default) disables all logging with zero overhead.

Compatible with any *slog.Logger, including slog.Default which bridges to the standard log package.

func (*Provider) WithMAC

func (p *Provider) WithMAC(filter ...MACFilter) *Provider

WithMAC includes network interface MAC addresses in the generation. An optional MACFilter controls which interfaces are included. Default is MACFilterPhysical, which excludes virtual, VPN, bridge, and container interfaces for stability.

func (*Provider) WithMotherboard

func (p *Provider) WithMotherboard() *Provider

WithMotherboard includes the motherboard serial number in the generation.

func (*Provider) WithSalt

func (p *Provider) WithSalt(salt string) *Provider

WithSalt sets a custom salt for additional entropy.

Example

ExampleProvider_WithSalt shows how a salt produces application-specific IDs.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/machineid"
)

func main() {
	ctx := context.Background()

	id1, _ := machineid.New().
		WithCPU().
		WithSystemUUID().
		WithSalt("app-one").
		ID(ctx)

	id2, _ := machineid.New().
		WithCPU().
		WithSystemUUID().
		WithSalt("app-two").
		ID(ctx)

	fmt.Printf("Same length: %v\n", len(id1) == len(id2))
	fmt.Printf("Different IDs: %v\n", id1 != id2)
}
Output:

Same length: true
Different IDs: true

func (*Provider) WithSystemUUID

func (p *Provider) WithSystemUUID() *Provider

WithSystemUUID includes the system UUID in the generation.

Directories

Path Synopsis
cmd
machineid command
internal
version
Package version provides build-time metadata for the CLI application.
Package version provides build-time metadata for the CLI application.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL