Skip to content

License Activation

Comprehensive guide to implementing license activation and instance tracking in your applications.

Overview

License activation allows you to track which instances are using a license, enforce activation limits, and provide better license management for your customers.

Basic Activation

Simple Activation

Activate a license for a specific instance:

bash
curl -X POST https://your-license-api.com/v1/activate-license \
  -H "Content-Type: application/json" \
  -d '{
    "key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
    "instance_id": "server-001"
  }'

Response:

json
{
  "activated": true,
  "alreadyActivated": false,
  "activations": {
    "current": 1,
    "max": 5
  },
  "instance": {
    "id": "server-001",
    "activatedAt": "2024-01-15T10:30:00Z"
  }
}

Activation with Instance Details

Provide additional instance information for better tracking:

bash
curl -X POST https://your-license-api.com/v1/activate-license \
  -H "Content-Type: application/json" \
  -d '{
    "key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
    "instance_id": "server-001",
    "instance_info": {
      "hostname": "prod-server-01.company.com",
      "ip_address": "192.168.1.100",
      "platform": "linux",
      "version": "1.2.3",
      "location": "datacenter-east"
    }
  }'

Instance ID Generation

Unique Instance Identifiers

Generate consistent, unique instance IDs:

javascript
const os = require('os');
const crypto = require('crypto');

class InstanceIDGenerator {
  static generate(includeTimestamp = false) {
    const hostname = os.hostname();
    const platform = os.platform();
    const arch = os.arch();
    const networkInterfaces = os.networkInterfaces();
    
    // Get MAC address from primary network interface
    const primaryInterface = this.getPrimaryNetworkInterface(networkInterfaces);
    const macAddress = primaryInterface?.mac || 'unknown';
    
    // Create base identifier
    const baseId = `${hostname}-${platform}-${arch}-${macAddress}`;
    
    // Add timestamp if requested (for unique instances)
    const uniqueId = includeTimestamp ? 
      `${baseId}-${Date.now()}` : 
      baseId;
    
    // Hash to create consistent, shorter ID
    return crypto
      .createHash('sha256')
      .update(uniqueId)
      .digest('hex')
      .substring(0, 16);
  }

  static getPrimaryNetworkInterface(interfaces) {
    // Find the primary network interface (usually eth0, en0, etc.)
    const priorities = ['eth0', 'en0', 'wlan0', 'wi-fi'];
    
    for (const name of priorities) {
      if (interfaces[name]) {
        return interfaces[name].find(iface => !iface.internal);
      }
    }
    
    // Fallback to first non-internal interface
    for (const [name, ifaces] of Object.entries(interfaces)) {
      const external = ifaces.find(iface => !iface.internal);
      if (external) {
        return external;
      }
    }
    
    return null;
  }

  static generateForContainer() {
    // For containerized environments
    const containerId = process.env.HOSTNAME || 
                       process.env.CONTAINER_ID || 
                       crypto.randomBytes(8).toString('hex');
    
    return crypto
      .createHash('sha256')
      .update(`container-${containerId}`)
      .digest('hex')
      .substring(0, 16);
  }

  static generateForCloud(provider = 'aws') {
    // For cloud environments - fetch instance metadata
    switch (provider.toLowerCase()) {
      case 'aws':
        return this.generateAWSInstanceId();
      case 'gcp':
        return this.generateGCPInstanceId();
      case 'azure':
        return this.generateAzureInstanceId();
      default:
        return this.generate(true);
    }
  }

  static async generateAWSInstanceId() {
    try {
      const fetch = require('node-fetch');
      const response = await fetch('http://169.254.169.254/latest/meta-data/instance-id', {
        timeout: 2000
      });
      
      if (response.ok) {
        const instanceId = await response.text();
        return `aws-${instanceId}`;
      }
    } catch (error) {
      console.warn('Failed to fetch AWS instance ID:', error.message);
    }
    
    return this.generate(true);
  }

  // Similar methods for GCP and Azure...
}

// Usage
const instanceId = InstanceIDGenerator.generate();
console.log('Instance ID:', instanceId);

Platform-Specific Generators

javascript
class PlatformSpecificInstanceId {
  static generate() {
    const platform = os.platform();
    
    switch (platform) {
      case 'win32':
        return this.generateWindows();
      case 'darwin':
        return this.generateMacOS();
      case 'linux':
        return this.generateLinux();
      default:
        return this.generateGeneric();
    }
  }

  static generateWindows() {
    const { execSync } = require('child_process');
    
    try {
      // Get Windows machine GUID
      const output = execSync(
        'wmic csproduct get uuid /format:value', 
        { encoding: 'utf8', timeout: 5000 }
      );
      
      const match = output.match(/UUID=(.+)/);
      if (match && match[1] && match[1].trim() !== '') {
        return `win-${match[1].trim()}`;
      }
    } catch (error) {
      console.warn('Failed to get Windows UUID:', error.message);
    }
    
    return this.generateGeneric();
  }

  static generateMacOS() {
    const { execSync } = require('child_process');
    
    try {
      // Get macOS hardware UUID
      const output = execSync(
        'system_profiler SPHardwareDataType | grep "Hardware UUID"',
        { encoding: 'utf8', timeout: 5000 }
      );
      
      const match = output.match(/Hardware UUID:\s*(.+)/);
      if (match && match[1]) {
        return `mac-${match[1].trim()}`;
      }
    } catch (error) {
      console.warn('Failed to get macOS UUID:', error.message);
    }
    
    return this.generateGeneric();
  }

  static generateLinux() {
    const fs = require('fs');
    
    try {
      // Try to read machine-id
      if (fs.existsSync('/etc/machine-id')) {
        const machineId = fs.readFileSync('/etc/machine-id', 'utf8').trim();
        return `linux-${machineId}`;
      }
      
      // Fallback to DMI product UUID
      if (fs.existsSync('/sys/class/dmi/id/product_uuid')) {
        const productUuid = fs.readFileSync('/sys/class/dmi/id/product_uuid', 'utf8').trim();
        return `linux-${productUuid}`;
      }
    } catch (error) {
      console.warn('Failed to get Linux machine ID:', error.message);
    }
    
    return this.generateGeneric();
  }

  static generateGeneric() {
    return InstanceIDGenerator.generate(true);
  }
}

Complete Activation Implementation

Robust Activation Manager

javascript
class LicenseActivationManager {
  constructor(licenseKey, apiUrl, options = {}) {
    this.licenseKey = licenseKey;
    this.apiUrl = apiUrl;
    this.instanceId = options.instanceId || InstanceIDGenerator.generate();
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.isActivated = false;
    this.activationInfo = null;
  }

  async activate() {
    if (this.isActivated) {
      return this.activationInfo;
    }

    try {
      const result = await this.performActivation();
      
      if (result.activated) {
        this.isActivated = true;
        this.activationInfo = result;
        console.log(`License activated for instance: ${this.instanceId}`);
        
        if (result.alreadyActivated) {
          console.log('Instance was already activated for this license');
        }
        
        return result;
      } else {
        throw new Error(result.error || 'Activation failed');
      }

    } catch (error) {
      console.error('License activation failed:', error.message);
      throw error;
    }
  }

  async performActivation(attempt = 1) {
    try {
      const instanceInfo = this.gatherInstanceInfo();
      
      const response = await fetch(`${this.apiUrl}/activate-license`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          key: this.licenseKey,
          instance_id: this.instanceId,
          instance_info: instanceInfo
        }),
        timeout: 30000
      });

      if (!response.ok) {
        const error = await response.json();
        
        if (response.status === 403 && error.error?.includes('Maximum activations reached')) {
          throw new Error(`Activation limit reached: ${error.error}`);
        }
        
        throw new Error(`HTTP ${response.status}: ${error.error || response.statusText}`);
      }

      return await response.json();

    } catch (error) {
      if (attempt < this.maxRetries && this.isRetriableError(error)) {
        console.warn(`Activation attempt ${attempt} failed, retrying in ${this.retryDelay}ms...`);
        await this.sleep(this.retryDelay * attempt);
        return this.performActivation(attempt + 1);
      }
      
      throw error;
    }
  }

  gatherInstanceInfo() {
    const os = require('os');
    
    return {
      hostname: os.hostname(),
      platform: os.platform(),
      arch: os.arch(),
      node_version: process.version,
      uptime: os.uptime(),
      total_memory: os.totalmem(),
      cpu_count: os.cpus().length,
      network_interfaces: this.getNetworkInfo(),
      process_id: process.pid,
      activation_time: new Date().toISOString()
    };
  }

  getNetworkInfo() {
    const interfaces = os.networkInterfaces();
    const info = {};
    
    for (const [name, ifaces] of Object.entries(interfaces)) {
      info[name] = ifaces
        .filter(iface => !iface.internal)
        .map(iface => ({
          address: iface.address,
          family: iface.family,
          mac: iface.mac
        }));
    }
    
    return info;
  }

  isRetriableError(error) {
    // Retry on network errors, not on license-specific errors
    return !error.message.includes('Maximum activations reached') &&
           !error.message.includes('Invalid license') &&
           !error.message.includes('License expired') &&
           !error.message.includes('License revoked');
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async deactivate() {
    if (!this.isActivated) {
      return { deactivated: false, error: 'Not activated' };
    }

    try {
      const response = await fetch(`${this.apiUrl}/revoke-license`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          key: this.licenseKey,
          revoked: true
        }),
        timeout: 30000
      });

      const result = await response.json();
      
      if (result.status === 'revoked') {
        this.isActivated = false;
        this.activationInfo = null;
        console.log(`License deactivated for instance: ${this.instanceId}`);
      }

      return result;

    } catch (error) {
      console.error('License deactivation failed:', error.message);
      throw error;
    }
  }

  async validateActivation() {
    if (!this.isActivated) {
      return { valid: false, error: 'Not activated' };
    }

    try {
      const response = await fetch(`${this.apiUrl}/validate-license`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          key: this.licenseKey,
          instance_id: this.instanceId
        }),
        timeout: 30000
      });

      return await response.json();

    } catch (error) {
      console.error('License validation failed:', error.message);
      return { valid: false, error: error.message };
    }
  }

  getActivationInfo() {
    return this.activationInfo;
  }

  getInstanceId() {
    return this.instanceId;
  }

  isLicenseActivated() {
    return this.isActivated;
  }
}

Activation Lifecycle Management

Application Integration

javascript
class ApplicationWithLicenseActivation {
  constructor(licenseKey, apiUrl) {
    this.activationManager = new LicenseActivationManager(licenseKey, apiUrl);
    this.isRunning = false;
    this.validationInterval = null;
  }

  async start() {
    try {
      // Activate license
      console.log('Activating license...');
      const activation = await this.activationManager.activate();
      
      console.log('License activation successful');
      console.log(`Current activations: ${activation.activations.current}/${activation.activations.max}`);
      
      // Start application
      this.isRunning = true;
      await this.startApplication();
      
      // Start periodic validation
      this.startPeriodicValidation();
      
      // Setup graceful shutdown
      this.setupShutdownHandlers();
      
    } catch (error) {
      console.error('Failed to start application:', error.message);
      process.exit(1);
    }
  }

  async startApplication() {
    console.log('Starting application services...');
    // Start your application logic here
    console.log('Application started successfully');
  }

  startPeriodicValidation(intervalMinutes = 60) {
    console.log(`Starting license validation every ${intervalMinutes} minutes`);
    
    this.validationInterval = setInterval(async () => {
      try {
        const validation = await this.activationManager.validateActivation();
        
        if (!validation.valid) {
          console.error('License validation failed:', validation.error);
          await this.handleInvalidLicense();
        } else {
          console.log('License validation successful');
        }

      } catch (error) {
        console.warn('License validation error:', error.message);
        // Don't immediately shut down on network errors
      }
    }, intervalMinutes * 60 * 1000);
  }

  async handleInvalidLicense() {
    console.log('License is no longer valid - initiating shutdown');
    await this.gracefulShutdown();
  }

  setupShutdownHandlers() {
    const gracefulShutdown = async (signal) => {
      console.log(`Received ${signal} - shutting down gracefully`);
      await this.gracefulShutdown();
    };

    process.on('SIGTERM', gracefulShutdown);
    process.on('SIGINT', gracefulShutdown);
    process.on('SIGQUIT', gracefulShutdown);
  }

  async gracefulShutdown() {
    if (!this.isRunning) return;
    
    console.log('Starting graceful shutdown...');
    this.isRunning = false;
    
    // Stop periodic validation
    if (this.validationInterval) {
      clearInterval(this.validationInterval);
      this.validationInterval = null;
    }
    
    try {
      // Stop application services
      await this.stopApplication();
      
      // Deactivate license
      console.log('Deactivating license...');
      await this.activationManager.deactivate();
      console.log('License deactivated');
      
    } catch (error) {
      console.error('Error during shutdown:', error.message);
    } finally {
      console.log('Shutdown complete');
      process.exit(0);
    }
  }

  async stopApplication() {
    console.log('Stopping application services...');
    // Stop your application logic here
    console.log('Application stopped');
  }
}

// Usage
const app = new ApplicationWithLicenseActivation(
  process.env.LICENSE_KEY,
  'https://your-license-api.com/v1'
);

app.start();

Activation Management for Different Scenarios

Desktop Applications

javascript
class DesktopAppActivation extends LicenseActivationManager {
  constructor(licenseKey, apiUrl) {
    super(licenseKey, apiUrl, {
      instanceId: InstanceIDGenerator.generate() // Persistent per machine
    });
    
    this.configFile = this.getConfigFilePath();
  }

  getConfigFilePath() {
    const os = require('os');
    const path = require('path');
    
    return path.join(os.homedir(), '.myapp', 'license.json');
  }

  async activate() {
    // Try to load existing activation
    const existing = await this.loadStoredActivation();
    if (existing && await this.validateStoredActivation(existing)) {
      this.isActivated = true;
      this.activationInfo = existing;
      console.log('Using existing license activation');
      return existing;
    }

    // Perform new activation
    const result = await super.activate();
    
    // Store activation info
    await this.storeActivation(result);
    
    return result;
  }

  async loadStoredActivation() {
    const fs = require('fs').promises;
    
    try {
      const data = await fs.readFile(this.configFile, 'utf8');
      return JSON.parse(data);
    } catch (error) {
      return null;
    }
  }

  async storeActivation(activation) {
    const fs = require('fs').promises;
    const path = require('path');
    
    try {
      await fs.mkdir(path.dirname(this.configFile), { recursive: true });
      await fs.writeFile(this.configFile, JSON.stringify(activation, null, 2));
    } catch (error) {
      console.warn('Failed to store activation:', error.message);
    }
  }

  async validateStoredActivation(activation) {
    // Quick validation of stored activation
    return activation.activated && 
           activation.instance?.id === this.instanceId &&
           new Date(activation.instance.activatedAt).getTime() > 0;
  }
}

Server Applications

javascript
class ServerAppActivation extends LicenseActivationManager {
  constructor(licenseKey, apiUrl, serverConfig = {}) {
    const instanceId = serverConfig.instanceId || 
                     process.env.INSTANCE_ID || 
                     InstanceIDGenerator.generate();
    
    super(licenseKey, apiUrl, { instanceId });
    
    this.serverConfig = serverConfig;
    this.healthCheckInterval = null;
  }

  async activate() {
    const result = await super.activate();
    
    // Start health reporting
    this.startHealthReporting();
    
    return result;
  }

  gatherInstanceInfo() {
    const baseInfo = super.gatherInstanceInfo();
    
    return {
      ...baseInfo,
      server_type: this.serverConfig.type || 'unknown',
      server_role: this.serverConfig.role || 'primary',
      datacenter: this.serverConfig.datacenter || 'unknown',
      environment: process.env.NODE_ENV || 'development',
      load_balancer_id: this.serverConfig.loadBalancerId,
      cluster_id: this.serverConfig.clusterId
    };
  }

  startHealthReporting(intervalMinutes = 30) {
    this.healthCheckInterval = setInterval(async () => {
      try {
        await this.reportHealth();
      } catch (error) {
        console.warn('Health reporting failed:', error.message);
      }
    }, intervalMinutes * 60 * 1000);
  }

  async reportHealth() {
    const health = this.gatherHealthInfo();
    
    try {
      console.log('Instance health:', health);
    } catch (error) {
      console.warn('Health check failed:', error.message);
    }
  }

  gatherHealthInfo() {
    const os = require('os');
    
    return {
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      memory_usage: process.memoryUsage(),
      cpu_usage: process.cpuUsage(),
      system_load: os.loadavg(),
      free_memory: os.freemem(),
      total_memory: os.totalmem()
    };
  }

  async deactivate() {
    if (this.healthCheckInterval) {
      clearInterval(this.healthCheckInterval);
      this.healthCheckInterval = null;
    }
    
    return super.deactivate();
  }
}

Handling Activation Limits

Smart Activation Management

javascript
class SmartActivationManager extends LicenseActivationManager {
  async activate() {
    try {
      return await super.activate();
    } catch (error) {
      if (error.message.includes('Maximum activations reached')) {
        return await this.handleActivationLimitReached();
      }
      throw error;
    }
  }

  async handleActivationLimitReached() {
    console.log('Activation limit reached, checking for inactive instances...');
    
    // Get current activations
    const activations = await this.getActivations();
    
    if (activations.length === 0) {
      throw new Error('No activations found but limit reached - contact support');
    }

    // Find potentially inactive instances
    const inactiveInstances = this.findInactiveInstances(activations);
    
    if (inactiveInstances.length > 0) {
      console.log(`Found ${inactiveInstances.length} inactive instances`);
      
      // Ask user which to deactivate
      const instanceToDeactivate = await this.selectInstanceToDeactivate(inactiveInstances);
      
      if (instanceToDeactivate) {
        await this.forceDeactivate(instanceToDeactivate.instance_id);
        return await super.activate(); // Retry activation
      }
    }

    // Show user all activations and let them choose
    return await this.handleManualDeactivation(activations);
  }

  async getActivations() {
    try {
      const response = await fetch(`${this.apiUrl}/list-activations`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ key: this.licenseKey }),
        timeout: 30000
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const data = await response.json();
      return data.activations || [];

    } catch (error) {
      console.warn('Failed to get activations:', error.message);
      return [];
    }
  }

  findInactiveInstances(activations) {
    const now = new Date();
    const inactiveThresholdHours = 24;
    
    return activations.filter(activation => {
      const lastSeen = new Date(activation.last_seen || activation.activated_at);
      const hoursSinceLastSeen = (now - lastSeen) / (1000 * 60 * 60);
      return hoursSinceLastSeen > inactiveThresholdHours;
    });
  }

  async selectInstanceToDeactivate(instances) {
    // In a real application, you'd show a UI dialog
    console.log('Inactive instances found:');
    instances.forEach((instance, index) => {
      console.log(`${index + 1}. ${instance.instance_id} (last seen: ${instance.last_seen})`);
    });
    
    // For CLI, you could use inquirer or similar
    // For GUI apps, show a selection dialog
    // For now, automatically select the oldest
    
    const oldest = instances.reduce((oldest, current) => {
      const currentLastSeen = new Date(current.last_seen || current.activated_at);
      const oldestLastSeen = new Date(oldest.last_seen || oldest.activated_at);
      return currentLastSeen < oldestLastSeen ? current : oldest;
    });
    
    console.log(`Automatically selecting oldest inactive instance: ${oldest.instance_id}`);
    return oldest;
  }

  async handleManualDeactivation(activations) {
    console.log('Current license activations:');
    activations.forEach((activation, index) => {
      console.log(`${index + 1}. ${activation.instance_id} (activated: ${activation.activated_at})`);
    });
    
    throw new Error(
      'License activation limit reached. Please deactivate an unused instance or upgrade your license.\n' +
      'Contact support if you need assistance managing your license activations.'
    );
  }

  async forceDeactivate(instanceId) {
    try {
      const response = await fetch(`${this.apiUrl}/revoke-license`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          key: this.licenseKey,
          revoked: true
        }),
        timeout: 30000
      });

      const result = await response.json();
      
      if (result.status === 'revoked') {
        console.log(`License revoked (was blocking instance: ${instanceId})`);
      } else {
        throw new Error(result.error || 'Revocation failed');
      }

    } catch (error) {
      console.error(`Failed to deactivate instance ${instanceId}:`, error.message);
      throw error;
    }
  }
}

Best Practices

1. Instance Management

  • Generate consistent instance IDs across application restarts
  • Include meaningful instance information for debugging
  • Handle activation limit scenarios gracefully

2. Error Handling

  • Distinguish between retriable and non-retriable errors
  • Provide clear error messages for activation failures
  • Implement fallback strategies for temporary failures

3. Performance

  • Cache activation status to avoid repeated API calls
  • Use appropriate timeouts for activation requests
  • Implement background validation for long-running applications

4. Security

  • Never store license keys in plain text
  • Secure instance identification methods
  • Validate activation status regularly

5. User Experience

  • Provide clear feedback during activation process
  • Show activation limit status to users
  • Offer self-service activation management

Next Steps