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
- Usage Tracking - Monitor license usage metrics
- Client Examples - Platform-specific implementations
- Error Handling - Comprehensive error management