Skip to content

Library and program to provides functionality to generate unique machine identifiers based on hardware characteristics

License

Notifications You must be signed in to change notification settings

slashdevops/machineid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

About

Library and program to provides functionality to generate unique machine identifiers based on hardware characteristics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Contributors 3

  •  
  •  
  •