All files / agent/src/registration machineId.ts

92.78% Statements 90/97
85.71% Branches 12/14
100% Functions 6/6
92.78% Lines 90/97

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 981x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 1x 1x 10x 10x 10x 1x 1x 1x 1x 1x 10x 10x 10x 10x 2x 2x 2x 10x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 10x 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x           8x 1x 1x 1x 1x 1x 4x 4x 2x 2x 2x 2x 4x 1x 1x 1x 1x 1x 3x 3x 2x 3x 1x 1x     1x 3x 1x  
/**
 * Machine ID Manager
 *
 * Generates and persists a unique machine identifier for agent registration.
 * Uses hardware-based ID generation with file-based persistence for consistency.
 *
 * Requirements: MVP.4.1.2
 */
 
import machineIdPkg from 'node-machine-id';
const { machineIdSync } = machineIdPkg;
import { promises as fs } from 'fs';
import { join } from 'path';
import os from 'os';
import { createLogger } from '../utils/logger.js';
 
const logger = createLogger('machineId');
 
export class MachineIdManager {
  private configDir: string;
  private idFile: string;
 
  constructor(configDir: string = join(os.homedir(), '.buildhive')) {
    this.configDir = configDir;
    this.idFile = join(configDir, 'machine-id');
  }
 
  /**
   * Get machine ID - loads from file or generates new one
   */
  async getMachineId(): Promise<string> {
    // Try to load from file first
    try {
      const storedId = await fs.readFile(this.idFile, 'utf8');
      if (storedId && storedId.trim()) {
        logger.info('Loaded machine ID from file');
        return storedId.trim();
      }
    } catch (error) {
      // File doesn't exist, will generate new
      logger.debug('No stored machine ID found, generating new one');
    }
 
    // Generate new machine ID based on hardware
    const machineId = machineIdSync(true);
    logger.info('Generated new machine ID');
 
    // Store for future use
    await this.storeMachineId(machineId);
 
    return machineId;
  }
 
  /**
   * Store machine ID to file for persistence
   */
  private async storeMachineId(machineId: string): Promise<void> {
    try {
      await fs.mkdir(this.configDir, { recursive: true });
      await fs.writeFile(this.idFile, machineId, 'utf8');
      await fs.chmod(this.idFile, 0o600); // Read/write for owner only
      logger.info(`Stored machine ID to ${this.idFile}`);
    } catch (error) {
      logger.error('Failed to store machine ID:', error);
      throw new Error(
        `Failed to store machine ID: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }
 
  /**
   * Check if machine ID exists
   */
  async exists(): Promise<boolean> {
    try {
      await fs.access(this.idFile);
      return true;
    } catch {
      return false;
    }
  }
 
  /**
   * Clear stored machine ID (for testing or reset)
   */
  async clear(): Promise<void> {
    try {
      await fs.unlink(this.idFile);
      logger.info('Cleared stored machine ID');
    } catch (error) {
      // Ignore if file doesn't exist
      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
        logger.error('Failed to clear machine ID:', error);
      }
    }
  }
}