@oletizi/lib-device-uuid
Version:
Cross-platform device UUID detection for Akai sampler backup tools
124 lines (123 loc) • 4.18 kB
JavaScript
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* Linux device detector using blkid and findmnt
*
* This detector uses findmnt to map mount paths to device paths,
* and blkid to extract device UUID and filesystem information.
*/
export class LinuxDetector {
/**
* Detect device information for a mount path on Linux
* @param mountPath - The path where the device is mounted
* @returns Device information extracted from blkid/findmnt
* @throws Error if device cannot be detected or platform not supported
*/
async detectDevice(mountPath) {
if (!this.isSupported()) {
throw new Error('LinuxDetector only works on Linux');
}
try {
// First, use findmnt to get device path from mount point
const devicePath = await this.getDevicePath(mountPath);
// Then use blkid to get device UUID and other info
const deviceInfo = await this.getDeviceInfo(devicePath);
return {
mountPath,
devicePath,
...deviceInfo,
};
}
catch (error) {
throw new Error(`Failed to detect device at ${mountPath}: ${error.message}`);
}
}
/**
* Get device path from mount path using findmnt
* @param mountPath - The mount path to resolve
* @returns Device path (e.g., /dev/sdb1)
* @throws Error if mount path not found
*/
async getDevicePath(mountPath) {
try {
// findmnt -n -o SOURCE /mount/path
// -n: no header
// -o SOURCE: output only device path
const { stdout } = await execAsync(`findmnt -n -o SOURCE "${mountPath}"`);
const devicePath = stdout.trim();
if (!devicePath) {
throw new Error(`No device found at mount path: ${mountPath}`);
}
return devicePath;
}
catch (error) {
throw new Error(`Failed to find device path: ${error.message}`);
}
}
/**
* Get device information using blkid
* @param devicePath - The device path (e.g., /dev/sdb1)
* @returns Partial device information
*/
async getDeviceInfo(devicePath) {
try {
// blkid -o export /dev/device
// -o export: output as KEY=VALUE pairs
const { stdout } = await execAsync(`blkid -o export "${devicePath}"`);
return this.parseBlkidOutput(stdout);
}
catch (error) {
// blkid may fail for some devices (e.g., no UUID), that's OK
// Return partial info with what we have
return {};
}
}
/**
* Parse blkid export format output
* @param output - The output from blkid -o export
* @returns Parsed device information
*/
parseBlkidOutput(output) {
const lines = output.split('\n');
const info = {};
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.includes('=')) {
continue;
}
const [key, value] = trimmed.split('=', 2);
// UUID=12345678-1234-1234-1234-123456789012
if (key === 'UUID') {
info.volumeUUID = value;
}
// LABEL=SDCARD
if (key === 'LABEL') {
info.volumeLabel = value;
}
// TYPE=vfat (filesystem type)
if (key === 'TYPE') {
info.filesystem = value;
}
// PARTUUID=12345678-02 (partition UUID, fallback if no volume UUID)
if (key === 'PARTUUID' && !info.volumeUUID) {
info.volumeSerial = value;
}
}
return info;
}
/**
* Check if Linux platform is supported
* @returns true if running on linux platform
*/
isSupported() {
return process.platform === 'linux';
}
/**
* Get the platform identifier
* @returns 'linux' platform constant
*/
getPlatform() {
return 'linux';
}
}