Skip to content

Commit 8edc9b2

Browse files
vadikabrianmcgillion
authored andcommitted
Add dynamic hostname generation for hardware-based device identification
Implement runtime-generated, human-readable hostnames derived from permanent hardware properties (DMI serial/UUID, MAC address, or machine-id). The hostname format is 'ghaf-NNNNNNNNNN' where N is a 10-digit number deterministically generated from hardware properties. Key features: - Host generates and shares hostname via /persist/common/ghaf/hostname - NetVM sets its actual hostname for DHCP client identification - All VMs have access to the hostname via environment variable - NetworkManager configured to preserve the dynamic hostname - 10 billion possible hostnames for excellent uniqueness - Replaces random device-id with deterministic hardware-based ID - Consolidates device-id and machine-id generation into single script - All machine-ids are deterministic and hardware-based (not random UUIDs) - Configurable ID source: hardware (default), static, or random Implementation: - dynamic-hostname.nix: Single script generates hostname, device-id, and all VM machine-ids - vm-hostname-export.nix: Exports hostname as environment variable - vm-hostname-setter.nix: Sets actual VM hostname from shared file - Virtiofs share (/persist/common -> /etc/common) propagates to VMs - NetworkManager hostname-mode set to 'none' to prevent override - dynamic-hostname.nix: Single script generates hostname, device-id, and all VM machine-i ds - vm-hostname-export.nix: Exports hostname as environment variable - vm-hostname-setter.nix: Sets actual VM hostname from shared file - Virtiofs share (/persist/common -> /etc/common) propagates to VMs - NetworkManager hostname-mode set to 'none' to prevent override - Replaces generate-device-id service from microvm-host.nix with single atomic operation - Uses writeShellApplication for better error handling and shellcheck validation - Code refactored with inherit (lib) for cleaner imports - Uses sysfs directly instead of iproute2 for MAC address reading Configuration options: - source = "hardware": Best-effort detection (DMI <E2><86><92> disk UUID <E2><86><92> MAC <E2><86><92> machine-id) - source = "static": User-provided value via staticValue option - source = "random": Random value generated once and persisted Technical details: - Hostname: ghaf-NNNNNNNNNN (10-digit CRC32-based hardware ID) - Device-id: Hardware ID formatted as hex string (e.g., 00-01-23-45-67) - Machine-ids: MD5 hash of hardware key + VM name (deterministic, not random) - Service runs before network-online.target for DHCP client identification - All identity files generated atomically in single service execution - Same hardware always produces same hostname, device-id, and machine-ids - Each VM gets unique but deterministic machine-id Use cases: - DHCP client identification with unique device names - Network troubleshooting and device tracking - Logging and monitoring across host and VMs - User visibility of hardware instance identity - Deterministic device identification across reboots - Reproducible VM identities for testing and automation - Privacy-focused deployments with random mode - Testing environments with static mode Documentation added at docs/src/content/docs/ghaf/dev/ref/dynamic-hostname.mdx Signed-off-by: Vadim Likholetov <[email protected]>
1 parent 1f425ed commit 8edc9b2

File tree

14 files changed

+665
-20
lines changed

14 files changed

+665
-20
lines changed

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default defineConfig({
9393
"ghaf/dev/ref/cosmic",
9494
"ghaf/dev/ref/idsvm-development",
9595
"ghaf/dev/ref/systemd-service-config",
96+
"ghaf/dev/ref/dynamic-hostname",
9697
"ghaf/dev/ref/kill_switch",
9798
"ghaf/dev/ref/wireguard-gui",
9899
],
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
---
2+
title: Dynamic Hostname Generation
3+
---
4+
5+
This module provides runtime-generated, human-readable hostnames based on permanent hardware properties like MAC address or computer serial number.
6+
7+
## Overview
8+
9+
The dynamic hostname system consists of three modules:
10+
11+
1. **`dynamic-hostname.nix`** - Generates and exports the hostname on the host system
12+
2. **`vm-hostname-export.nix`** - Makes the hostname available to VMs via environment variables
13+
3. **`vm-hostname-setter.nix`** - Sets the actual VM hostname from the shared hostname file
14+
15+
## Features
16+
17+
- **Hardware-based**: Deterministic hostname generation from stable hardware properties (DMI serial/UUID, MAC address, or machine-id as fallback)
18+
- **Human-readable**: Format like `ghaf-1234567890` where digits are derived from hardware
19+
- **Propagated**: Automatically shared with guest VMs through `/persist/common` virtiofs mount
20+
- **Non-invasive**: Keeps static `networking.hostName` unchanged on host for network topology
21+
- **Network-ready**: NetVM uses the dynamic hostname for DHCP and external network services
22+
- **Fully deterministic**: Hostname, device-id, and all VM machine-ids derived from hardware (no random values)
23+
24+
## Usage
25+
26+
### On Host
27+
28+
Enable the dynamic hostname module in your host configuration:
29+
30+
```nix
31+
{
32+
ghaf.identity.dynamicHostName = {
33+
enable = true;
34+
source = "hardware"; # Optional: "hardware" (default), "static", or "random"
35+
prefix = "ghaf"; # Optional: default is "ghaf"
36+
digits = 10; # Optional: default is 10
37+
};
38+
}
39+
```
40+
41+
**Alternative configurations:**
42+
43+
For static hardware ID (manual configuration):
44+
```nix
45+
{
46+
ghaf.identity.dynamicHostName = {
47+
enable = true;
48+
source = "static";
49+
staticValue = "my-unique-identifier";
50+
};
51+
}
52+
```
53+
54+
For random ID (generated once and persisted):
55+
```nix
56+
{
57+
ghaf.identity.dynamicHostName = {
58+
enable = true;
59+
source = "random";
60+
};
61+
}
62+
```
63+
64+
This will:
65+
- Generate a hostname like `ghaf-1234567890` at boot
66+
- Export it to `/run/ghaf-hostname` and `/var/lib/ghaf/identity/hostname`
67+
- Share it via `/persist/common/ghaf/hostname` for VMs
68+
- Make `$GHAF_HOSTNAME` environment variable available in shells
69+
- Host keeps static hostname "ghaf-host" for internal networking
70+
71+
### In VMs
72+
73+
**For VMs that need the hostname as an environment variable only (e.g., GUIVM):**
74+
75+
```nix
76+
{
77+
ghaf.identity.vmHostNameExport = {
78+
enable = true;
79+
};
80+
}
81+
```
82+
83+
This makes the `$GHAF_HOSTNAME` environment variable available in the VM, reading from `/etc/common/ghaf/hostname`.
84+
85+
**For VMs that need to set their actual hostname (e.g., NetVM for DHCP/network services):**
86+
87+
```nix
88+
{
89+
ghaf.identity.vmHostNameExport = {
90+
enable = true;
91+
};
92+
ghaf.identity.vmHostNameSetter = {
93+
enable = true;
94+
};
95+
}
96+
```
97+
98+
This sets the VM's actual hostname from the shared file, which is useful for network services like DHCP client identification. It also configures NetworkManager (if enabled) to not override the hostname.
99+
100+
## Hardware ID Source Options
101+
102+
The module supports three different sources for generating the hardware ID:
103+
104+
### hardware (default)
105+
106+
Best-effort hardware detection that tries multiple sources in this priority order:
107+
108+
1. DMI product serial (`/sys/class/dmi/id/product_serial`)
109+
2. DMI product UUID (`/sys/class/dmi/id/product_uuid`)
110+
3. Disk UUID (first available from `/dev/disk/by-uuid/`)
111+
4. First non-loopback MAC address (from `/sys/class/net/*/address`)
112+
5. Machine ID (`/etc/machine-id`) as last resort
113+
114+
### static
115+
116+
Use a user-provided static value. Requires setting `staticValue`:
117+
118+
```nix
119+
ghaf.identity.dynamicHostName.source = "static";
120+
ghaf.identity.dynamicHostName.staticValue = "my-unique-id";
121+
```
122+
123+
### random
124+
125+
Generate a random value on first boot and persist it to `/var/lib/ghaf/identity/random-seed`. The same random value is used across reboots:
126+
127+
```nix
128+
ghaf.identity.dynamicHostName.source = "random";
129+
```
130+
131+
## Implementation Details
132+
133+
- Uses CRC32 checksum modulo 10^N (default N=10) for deterministic digit generation
134+
- Runs as a systemd oneshot service early in boot on the host
135+
- Static `networking.hostName` remains as "ghaf-host" on the host for internal network topology
136+
- Host does not change its own hostname - only generates and shares the identity
137+
- NetVM sets its actual hostname from the shared file for external network services
138+
- VMs receive the hostname via existing `/persist/common``/etc/common` virtiofs share
139+
- Environment variables are set via `environment.extraInit` for reliable shell initialization
140+
- NetworkManager is configured with `hostname-mode = "none"` to prevent overriding the dynamic hostname
141+
- The hostname setter service runs before NetworkManager to ensure proper DHCP client identification
142+
- All identity generation (hostname, device-id, machine-ids) happens atomically in single script
143+
- VM machine-ids are deterministic MD5 hashes of (hardware key + VM name), not random UUIDs
144+
- Uses sysfs directly for hardware detection (no dependency on iproute2 or other heavy tools)
145+
146+
## Files Generated
147+
148+
### On Host
149+
150+
- `/run/ghaf-hostname` - Symlink to current hostname
151+
- `/var/lib/ghaf/identity/hostname` - Generated hostname
152+
- `/var/lib/ghaf/identity/id` - Numeric ID portion
153+
- `/persist/common/ghaf/hostname` - Shared with VMs
154+
- `/persist/common/ghaf/id` - Shared numeric ID
155+
- `/persist/common/device-id` - Hardware-based device ID (hex format with dashes)
156+
- `/persist/storagevm/<vm-name>/etc/machine-id` - Deterministic machine-id for each VM
157+
158+
### In VMs
159+
160+
- `/etc/common/ghaf/hostname` - Mounted from host via virtiofs
161+
- `/etc/common/ghaf/id` - Mounted from host via virtiofs
162+
- `$GHAF_HOSTNAME` - Environment variable
163+
- NetworkManager configuration (if `vmHostNameSetter` is enabled) - Prevents hostname override
164+
165+
## Example
166+
167+
On a laptop with serial number `ABC123XYZ`:
168+
169+
```bash
170+
# On ghaf-host
171+
$ cat /run/ghaf-hostname
172+
ghaf-1234567890
173+
174+
$ hostname
175+
ghaf-host
176+
177+
$ echo $GHAF_HOSTNAME
178+
ghaf-1234567890
179+
180+
# On NetVM
181+
$ cat /etc/common/ghaf/hostname
182+
ghaf-1234567890
183+
184+
$ hostname
185+
ghaf-1234567890
186+
187+
$ echo $GHAF_HOSTNAME
188+
ghaf-1234567890
189+
190+
# In GUIVM
191+
$ cat /etc/common/ghaf/hostname
192+
ghaf-1234567890
193+
194+
$ hostname
195+
gui-vm
196+
197+
$ echo $GHAF_HOSTNAME
198+
ghaf-1234567890
199+
```
200+
201+
## Collision Probability
202+
203+
With 10 digits (default), there are 10 billion possible hostnames, which provides excellent uniqueness even for very large fleets. If you need fewer digits for brevity, you can reduce the `digits` option:
204+
205+
```nix
206+
ghaf.identity.dynamicHostName.digits = 6; # 1 million possibilities
207+
```
208+
209+
## Notes
210+
211+
- The static `networking.hostName = "ghaf-host"` is intentionally unchanged on the host to preserve internal network topology and host mappings
212+
- The host does not change its own hostname - it only generates the identity
213+
- NetVM sets its hostname to the generated value for use in DHCP client identification and external network services
214+
- Other VMs (like GUIVM) keep their static hostnames but have access to the hardware-based identity via environment variables
215+
- The shared directory `/persist/common` must exist on the host (automatically created by the module)
216+
217+
## Use Cases
218+
219+
- **DHCP Client Identification**: NetVM uses the unique hostname when requesting DHCP leases, allowing network administrators to identify specific devices
220+
- **Network Service Identification**: External services see a consistent, hardware-based hostname that persists across reboots
221+
- **Logging and Monitoring**: All VMs can include the hardware-based identity in logs via `$GHAF_HOSTNAME`
222+
- **User Visibility**: Users can identify their specific hardware instance across host and VMs
223+
- **Network Troubleshooting**: Consistent hostname helps with debugging network issues and tracking device behavior
224+
- **Reproducible Testing**: Deterministic identities allow consistent testing environments across rebuilds
225+
- **Fleet Management**: Same hardware always produces same identities for reliable device tracking
226+
227+
## Technical Details
228+
229+
### NetworkManager Integration
230+
231+
When `vm-hostname-setter` is enabled on a VM with NetworkManager, the module:
232+
233+
1. Sets `networking.networkmanager.settings.main.hostname-mode = "none"` to prevent NetworkManager from managing the hostname
234+
2. Ensures the `set-dynamic-hostname` service runs before `NetworkManager.service`
235+
3. Allows NetworkManager to use the hardware-based hostname for DHCP requests without modifying it
236+
237+
This ensures that the dynamic hostname is preserved even when NetworkManager obtains a DHCP lease or connects to different networks.
238+
239+
### Virtiofs Share
240+
241+
The `/persist/common` directory on the host is mounted as `/etc/common` in VMs using virtiofs. This share must be configured in the VM's `microvm.shares` configuration:
242+
243+
```nix
244+
{
245+
tag = "ghaf-common";
246+
source = "/persist/common";
247+
mountPoint = "/etc/common";
248+
proto = "virtiofs";
249+
}
250+
```
251+
252+
NetVM and GUIVM have this share configured automatically.

modules/common/flake-module.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
./services
1818
./networking
1919
./logging
20+
./identity
2021
];
2122
};
2223
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-FileCopyrightText: 2022-2026 TII (SSRC) and the Ghaf contributors
2+
# SPDX-License-Identifier: Apache-2.0
3+
{
4+
imports = [
5+
./dynamic-hostname.nix
6+
./vm-hostname-export.nix
7+
./vm-hostname-setter.nix
8+
];
9+
}

0 commit comments

Comments
 (0)