Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/installer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aiox-squads/installer",
"version": "3.3.8",
"version": "3.3.9",
"description": "AIOX Installer - Automated setup wizard for AIOX projects (greenfield & brownfield)",
"main": "src/index.js",
"bin": {
Expand Down Expand Up @@ -42,6 +42,7 @@
"fs-extra": "^11.3.2",
"inquirer": "^8.2.6",
"js-yaml": "^4.1.0",
"node-machine-id": "^1.1.12",
"ora": "^5.4.1",
"semver": "^7.7.2"
},
Expand Down
31 changes: 31 additions & 0 deletions packages/installer/src/wizard/pro-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ function resolveLicenseServerUrl(rawUrl = DEFAULT_LICENSE_SERVER_URL) {

const LICENSE_SERVER_URL = resolveLicenseServerUrl(process.env.AIOX_LICENSE_API_URL);
const PASSWORD_RESET_URL = new URL('/reset-password', LICENSE_SERVER_URL).toString();
const MACHINE_ID_HASH_PREFIX = 'aiox-pro-native-machine-id:v1:';

/**
* Inline License Client — lightweight HTTP client for pre-bootstrap license checks.
Expand Down Expand Up @@ -612,6 +613,34 @@ function generateMachineId() {
return licenseCryptoModule.generateMachineId();
}

const nativeMachineId = generateNativeMachineId();
if (nativeMachineId) {
return nativeMachineId;
}

return generateLegacyMachineId();
}

function generateNativeMachineId() {
const crypto = require('crypto');
try {
const { machineIdSync } = require('node-machine-id');
const nativeMachineId = machineIdSync(true);

if (!nativeMachineId || typeof nativeMachineId !== 'string') {
throw new Error('Native machine id unavailable');
}

return crypto
.createHash('sha256')
.update(`${MACHINE_ID_HASH_PREFIX}${nativeMachineId}`)
.digest('hex');
} catch {
return null;
}
}

function generateLegacyMachineId() {
const crypto = require('crypto');
const os = require('os');
const components = [];
Expand Down Expand Up @@ -2322,6 +2351,8 @@ module.exports = {
resolveProSourceDir,
InlineLicenseClient,
generateMachineId,
generateNativeMachineId,
generateLegacyMachineId,
persistLicenseCache,
resolveLicenseServerUrl,
resolveNpmInvocation,
Expand Down
46 changes: 46 additions & 0 deletions packages/installer/tests/unit/pro-setup-machine-id.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const crypto = require('crypto');
const { machineIdSync } = require('node-machine-id');

const { _testing } = require('@aiox-squads/installer/pro-setup');

describe('pro setup machine id', () => {
afterEach(() => {
jest.dontMock('node-machine-id');
jest.dontMock('os');
});

test('uses the same native machine id fingerprint as the Pro runtime', () => {
const nativeMachineId = machineIdSync(true);
const expected = crypto
.createHash('sha256')
.update(`aiox-pro-native-machine-id:v1:${nativeMachineId}`)
.digest('hex');

expect(_testing.generateMachineId()).toBe(expected);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test('falls back to the legacy fingerprint when native machine id is unavailable', () => {
jest.doMock('node-machine-id', () => ({
machineIdSync: jest.fn(() => {
throw new Error('Native machine id unavailable');
}),
}));
jest.doMock('os', () => ({
hostname: jest.fn(() => 'fallback-host'),
cpus: jest.fn(() => [{ model: 'fallback-cpu' }]),
networkInterfaces: jest.fn(() => ({
en0: [{ internal: false, mac: 'aa:bb:cc:dd:ee:ff' }],
})),
}));

jest.isolateModules(() => {
const { _testing: isolatedTesting } = require('@aiox-squads/installer/pro-setup');
const expected = crypto
.createHash('sha256')
.update('fallback-host|fallback-cpu|aa:bb:cc:dd:ee:ff')
.digest('hex');

expect(isolatedTesting.generateMachineId()).toBe(expected);
});
});
});
Loading