diff --git a/modules/macOSHelpers.js b/modules/macOSHelpers.js index 46725f82..221c09b5 100644 --- a/modules/macOSHelpers.js +++ b/modules/macOSHelpers.js @@ -1,5 +1,17 @@ /* -Copyright 2024 +Copyright 2024 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. macOS Platform Helper Functions Centralizes macOS-specific utilities for bundle detection, service naming, and system operations @@ -9,7 +21,8 @@ Centralizes macOS-specific utilities for bundle detection, service naming, and s // CONSTANTS // ============================================================================ -var MACOS_PATHS = { +var MACOS_PATHS = +{ LAUNCH_DAEMONS: '/Library/LaunchDaemons/', LAUNCH_AGENTS: '/Library/LaunchAgents/', SYSTEM_LAUNCH_DAEMONS: '/System/Library/LaunchDaemons/', @@ -18,12 +31,14 @@ var MACOS_PATHS = { LAUNCHCTL: '/bin/launchctl' }; -var LAUNCHD_DOMAINS = { +var LAUNCHD_DOMAINS = +{ SYSTEM: 'system', GUI_PREFIX: 'gui/' }; -var BUNDLE_STRUCTURE = { +var BUNDLE_STRUCTURE = +{ CONTENTS_PATH: '.app/Contents/', MACOS_PATH: '.app/Contents/MacOS/', RESOURCES_PATH: '.app/Contents/Resources/' @@ -34,8 +49,12 @@ var BUNDLE_STRUCTURE = { // ============================================================================ // Check if a given path is from an app bundle -function isRunningFromBundle(execPath) { - if (!execPath) execPath = process.execPath; +function isRunningFromBundle(execPath) +{ + if (!execPath) + { + execPath = process.execPath; + } var indexResult = execPath.indexOf('.app/Contents/MacOS/'); @@ -46,9 +65,16 @@ function isRunningFromBundle(execPath) { // Extract the parent directory of a bundle (e.g., /opt/meshagent/ from /opt/meshagent/MeshAgent.app/Contents/MacOS/meshagent) // Returns null if not a bundle path -function getBundleParentDirectory(execPath) { - if (!execPath) execPath = process.execPath; - if (!isRunningFromBundle(execPath)) return null; +function getBundleParentDirectory(execPath) +{ + if (!execPath) + { + execPath = process.execPath; + } + if (!isRunningFromBundle(execPath)) + { + return null; + } var parts = execPath.split('.app/Contents/MacOS/')[0].split('/'); parts.pop(); // Remove bundle name @@ -58,9 +84,11 @@ function getBundleParentDirectory(execPath) { // Extract the bundle path from a binary path // e.g., /opt/meshagent/MeshAgent.app/Contents/MacOS/meshagent -> /opt/meshagent/MeshAgent.app // Returns null if not a bundle path -function getBundlePathFromBinaryPath(binaryPath) { +function getBundlePathFromBinaryPath(binaryPath) +{ - if (!isRunningFromBundle(binaryPath)) { + if (!isRunningFromBundle(binaryPath)) + { return null; } @@ -74,8 +102,12 @@ function getBundlePathFromBinaryPath(binaryPath) { // Sanitize identifier to follow reverse DNS naming conventions // Only allow alphanumeric, hyphens, and underscores (dots will be added between components) -function sanitizeIdentifier(str) { - if (!str) return null; +function sanitizeIdentifier(str) +{ + if (!str) + { + return null; + } // Replace spaces with hyphens, remove all non-alphanumeric except hyphens/underscores, convert to lowercase return str.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase(); } @@ -88,18 +120,21 @@ function sanitizeIdentifier(str) { // - meshagent.ServiceName (custom service name only) // - meshagent (default service name only) // - ServiceName (non-macOS platforms) -function buildServiceId(serviceName, companyName, options) { +function buildServiceId(serviceName, companyName, options) +{ options = options || {}; var platform = options.platform || process.platform; var explicitServiceId = options.explicitServiceId || null; // If an explicit serviceId is provided, use it directly - if (explicitServiceId !== null) { + if (explicitServiceId !== null) + { return explicitServiceId; } // Non-macOS platforms use simple sanitized identifier - if (platform !== 'darwin') { + if (platform !== 'darwin') + { return sanitizeIdentifier(serviceName); } @@ -107,19 +142,27 @@ function buildServiceId(serviceName, companyName, options) { var sanitizedServiceName = sanitizeIdentifier(serviceName); var sanitizedCompanyName = sanitizeIdentifier(companyName); - if (sanitizedCompanyName) { + if (sanitizedCompanyName) + { // Company name present - if (sanitizedServiceName && sanitizedServiceName !== 'meshagent') { + if (sanitizedServiceName && sanitizedServiceName !== 'meshagent') + { // Custom service name + company: meshagent.ServiceName.CompanyName return 'meshagent.' + sanitizedServiceName + '.' + sanitizedCompanyName; - } else { + } + else + { // Default service name + company: meshagent.CompanyName return 'meshagent.' + sanitizedCompanyName; } - } else if (sanitizedServiceName && sanitizedServiceName !== 'meshagent') { + } + else if (sanitizedServiceName && sanitizedServiceName !== 'meshagent') + { // Only custom service name (no company): meshagent.ServiceName return 'meshagent.' + sanitizedServiceName; - } else { + } + else + { // Default service name only: meshagent return 'meshagent'; } @@ -131,10 +174,14 @@ function buildServiceId(serviceName, companyName, options) { // Get the plist path for a given service ID and type // type: 'daemon' for LaunchDaemon, 'agent' for LaunchAgent -function getPlistPath(serviceId, type) { - if (type === 'daemon') { +function getPlistPath(serviceId, type) +{ + if (type === 'daemon') + { return MACOS_PATHS.LAUNCH_DAEMONS + serviceId + '.plist'; - } else if (type === 'agent') { + } + else if (type === 'agent') + { return MACOS_PATHS.LAUNCH_AGENTS + serviceId + '-agent.plist'; } return null; @@ -146,8 +193,10 @@ function getPlistPath(serviceId, type) { // Get the launchd domain for a given UID // Returns 'system' for system domain (uid=null), or 'gui/{uid}' for user domain -function getLaunchdDomain(uid) { - if (uid === null || uid === undefined) { +function getLaunchdDomain(uid) +{ + if (uid === null || uid === undefined) + { return LAUNCHD_DOMAINS.SYSTEM; } return LAUNCHD_DOMAINS.GUI_PREFIX + uid; @@ -155,7 +204,8 @@ function getLaunchdDomain(uid) { // Build a launchd service path in the format 'domain/serviceId' // Used for launchctl commands like 'launchctl print system/meshagent' -function getLaunchdPath(domain, serviceId) { +function getLaunchdPath(domain, serviceId) +{ return domain + '/' + serviceId; } @@ -165,14 +215,16 @@ function getLaunchdPath(domain, serviceId) { // Copy an app bundle using ditto (preserves all macOS metadata, signatures, etc.) // Returns true on success, throws error on failure -function copyBundleWithDitto(sourcePath, targetPath) { +function copyBundleWithDitto(sourcePath, targetPath) +{ var child_process = require('child_process'); var fs = require('fs'); var dittoError = null; var child = child_process.execFile(MACOS_PATHS.DITTO, ['ditto', sourcePath, targetPath]); - child.stderr.on('data', function(d) { + child.stderr.on('data', function(d) + { dittoError = d.toString(); process.stderr.write(d); }); @@ -180,7 +232,8 @@ function copyBundleWithDitto(sourcePath, targetPath) { child.waitExit(); // Verify the copy succeeded by checking if target exists - if (dittoError || !fs.existsSync(targetPath)) { + if (dittoError || !fs.existsSync(targetPath)) + { throw new Error('Bundle copy failed: ' + (dittoError || 'Target not created')); } @@ -189,9 +242,10 @@ function copyBundleWithDitto(sourcePath, targetPath) { // Execute PlistBuddy command on a plist file // Returns the output string, throws on error -function executePlistBuddy(command, plistPath) { +function executePlistBuddy(command, plistPath) +{ var child_process = require('child_process'); - return child_process.execSync(MACOS_PATHS.PLIST_BUDDY + ' -c "' + command + '" "' + plistPath + '"', { + return child_process.execFileSync(MACOS_PATHS.PLIST_BUDDY, ['-c', command, plistPath], { encoding: 'utf8' }).trim(); } @@ -200,7 +254,8 @@ function executePlistBuddy(command, plistPath) { // EXPORTS // ============================================================================ -module.exports = { +module.exports = +{ // Constants PATHS: MACOS_PATHS, DOMAINS: LAUNCHD_DOMAINS, @@ -226,3 +281,4 @@ module.exports = { copyBundleWithDitto: copyBundleWithDitto, executePlistBuddy: executePlistBuddy }; +