Pushed by: admin License: 1CFBBDCA-31F (Starter) Timestamp: 2026-02-01T00:05:57.898363masterdev
parent
29f638f8f6
commit
a7c906e576
File diff suppressed because it is too large
Load Diff
@ -1,368 +1,598 @@
|
||||
// ============================================
|
||||
// SYSTÈME DE VALIDATION DE LICENCE
|
||||
// SYSTÈME DE VALIDATION DE LICENCE - VERSION 2.0
|
||||
// Avec support fichier .lic
|
||||
// ============================================
|
||||
|
||||
const LICENSE_STORAGE_KEY = 'git_pusher_license';
|
||||
const LICENSE_API_URL = window.location.protocol + '//' + window.location.hostname + ':9999';
|
||||
|
||||
function initializeLicense() {
|
||||
console.log("Initializing license system...");
|
||||
// État global de la licence
|
||||
let currentLicense = null;
|
||||
let currentHostname = null;
|
||||
|
||||
// Vérifier si une licence est déjà stockée
|
||||
const storedLicense = getCookie(LICENSE_STORAGE_KEY);
|
||||
// ============================================
|
||||
// INITIALISATION
|
||||
// ============================================
|
||||
|
||||
if (storedLicense) {
|
||||
// Valider la licence stockée
|
||||
validateStoredLicense(storedLicense);
|
||||
// Afficher les infos de licence
|
||||
displayLicenseInfo(storedLicense);
|
||||
} else {
|
||||
// Afficher la page de licence
|
||||
showLicenseModal();
|
||||
}
|
||||
async function initializeLicense() {
|
||||
console.log("Initializing license system v2.0...");
|
||||
|
||||
try {
|
||||
// Récupérer le statut de la licence depuis le serveur
|
||||
const response = await fetch(`${LICENSE_API_URL}/license/status`);
|
||||
const data = await response.json();
|
||||
|
||||
console.log("License status:", data);
|
||||
|
||||
currentHostname = data.hostname;
|
||||
|
||||
if (data.status === 'valid' && data.license) {
|
||||
// Licence valide
|
||||
currentLicense = data.license;
|
||||
displayLicenseInfo(data.license);
|
||||
hideLicenseModal();
|
||||
} else {
|
||||
// Pas de licence ou licence invalide
|
||||
showLicenseModal(data.error, data.error_code, data.hostname);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking license:", error);
|
||||
// En cas d'erreur réseau, afficher le modal
|
||||
showLicenseModal("Impossible de vérifier la licence. Le serveur est-il démarré?", "CONNECTION_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// AFFICHAGE DU BADGE DE LICENCE
|
||||
// ============================================
|
||||
|
||||
function displayLicenseInfo(license) {
|
||||
console.log("Displaying license info...");
|
||||
|
||||
// Chercher le container du badge
|
||||
const container = document.getElementById('license-badge-container');
|
||||
if (!container) {
|
||||
console.error("license-badge-container not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer le badge
|
||||
const badge = document.createElement('div');
|
||||
badge.id = 'license-badge';
|
||||
badge.style.cssText = `
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-align: center;
|
||||
min-width: 200px;
|
||||
`;
|
||||
|
||||
let badgeText = '✓ Licence Activée';
|
||||
|
||||
// Si c'est une licence d'essai
|
||||
if (license.startsWith('TRIAL-')) {
|
||||
const daysRemaining = getTrialDaysRemaining(license);
|
||||
console.log("Displaying license info:", license);
|
||||
|
||||
if (daysRemaining <= 0) {
|
||||
badgeText = '⏱️ Essai expiré';
|
||||
badge.style.background = 'linear-gradient(135deg, #f44336 0%, #da190b 100%)';
|
||||
} else if (daysRemaining <= 2) {
|
||||
badgeText = `⚠️ ${daysRemaining} jour${daysRemaining > 1 ? 's' : ''} restant${daysRemaining > 1 ? 's' : ''}`;
|
||||
badge.style.background = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)';
|
||||
} else {
|
||||
badgeText = `⏱️ Essai: ${daysRemaining} jours`;
|
||||
const container = document.getElementById('license-badge-container');
|
||||
if (!container) {
|
||||
console.error("license-badge-container not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
badge.textContent = badgeText;
|
||||
badge.onclick = function() {
|
||||
alert('Licence: ' + license.substring(0, 50) + '...\n\nClique sur le logo pour gérer ta licence.');
|
||||
};
|
||||
// Supprimer l'ancien badge s'il existe
|
||||
const oldBadge = document.getElementById('license-badge');
|
||||
if (oldBadge) oldBadge.remove();
|
||||
|
||||
container.appendChild(badge);
|
||||
// Créer le badge
|
||||
const badge = document.createElement('div');
|
||||
badge.id = 'license-badge';
|
||||
|
||||
// Ajouter un hover effect
|
||||
badge.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-3px)';
|
||||
this.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.5)';
|
||||
});
|
||||
// Déterminer le style selon le type et les jours restants
|
||||
let badgeStyle = '';
|
||||
let badgeText = '';
|
||||
let badgeIcon = '✓';
|
||||
|
||||
badge.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
this.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.3)';
|
||||
});
|
||||
}
|
||||
const daysRemaining = license.days_remaining || 0;
|
||||
const licenseType = license.type_name || license.type || 'Unknown';
|
||||
|
||||
function getTrialDaysRemaining(trialLicense) {
|
||||
// Extraire le timestamp du license (format: TRIAL-timestamp)
|
||||
const parts = trialLicense.split('-');
|
||||
if (parts.length !== 2) return 0;
|
||||
if (daysRemaining <= 0) {
|
||||
// Expirée
|
||||
badgeStyle = 'background: linear-gradient(135deg, #f44336 0%, #da190b 100%);';
|
||||
badgeText = '⚠ Licence expirée';
|
||||
badgeIcon = '⚠';
|
||||
} else if (daysRemaining <= 7) {
|
||||
// Expire bientôt
|
||||
badgeStyle = 'background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);';
|
||||
badgeText = `⚠ ${licenseType} - ${daysRemaining}j restants`;
|
||||
badgeIcon = '⚠';
|
||||
} else if (license.type === 'trial') {
|
||||
// Essai
|
||||
badgeStyle = 'background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);';
|
||||
badgeText = `⏱ Essai - ${daysRemaining}j restants`;
|
||||
badgeIcon = '⏱';
|
||||
} else {
|
||||
// Licence normale valide
|
||||
badgeStyle = 'background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);';
|
||||
badgeText = `✓ ${licenseType}`;
|
||||
badgeIcon = '✓';
|
||||
}
|
||||
|
||||
const timestamp = parseInt(parts[1]);
|
||||
if (isNaN(timestamp)) return 0;
|
||||
badge.style.cssText = `
|
||||
${badgeStyle}
|
||||
color: white;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
badge.innerHTML = `
|
||||
<span style="font-size: 14px;">${badgeIcon}</span>
|
||||
<span>${badgeText}</span>
|
||||
`;
|
||||
|
||||
// Click pour voir les détails
|
||||
badge.onclick = function() {
|
||||
showLicenseDetails(license);
|
||||
};
|
||||
|
||||
// Hover effect
|
||||
badge.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
this.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)';
|
||||
});
|
||||
|
||||
badge.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
this.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
|
||||
});
|
||||
|
||||
container.appendChild(badge);
|
||||
}
|
||||
|
||||
// Créer la date de création
|
||||
const createdDate = new Date(timestamp);
|
||||
// ============================================
|
||||
// MODAL DE DÉTAILS DE LICENCE
|
||||
// ============================================
|
||||
|
||||
// Ajouter 7 jours
|
||||
const expirationDate = new Date(createdDate.getTime() + (7 * 24 * 60 * 60 * 1000));
|
||||
function showLicenseDetails(license) {
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'license-details-modal';
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
`;
|
||||
|
||||
const features = (license.features || []).map(f => `<span style="background: #e3f2fd; color: #1565c0; padding: 4px 8px; border-radius: 4px; font-size: 11px; margin: 2px;">${f}</span>`).join('');
|
||||
|
||||
const limits = license.limits || {};
|
||||
const maxApps = limits.max_apps === -1 ? 'Illimité' : limits.max_apps;
|
||||
const maxPushes = limits.max_pushes_per_day === -1 ? 'Illimité' : limits.max_pushes_per_day + '/jour';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div style="background: white; border-radius: 16px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0; color: #333;">📋 Détails de la licence</h2>
|
||||
<button onclick="this.closest('#license-details-modal').remove()" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
|
||||
</div>
|
||||
|
||||
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<div>
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase;">Type</div>
|
||||
<div style="font-weight: 600; color: #333;">${license.type_name || license.type}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase;">ID</div>
|
||||
<div style="font-weight: 600; color: #333; font-family: monospace;">${license.license_id}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase;">Expire</div>
|
||||
<div style="font-weight: 600; color: #333;">${license.expires}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase;">Jours restants</div>
|
||||
<div style="font-weight: 600; color: ${license.days_remaining <= 7 ? '#f44336' : '#4caf50'};">${license.days_remaining}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase; margin-bottom: 5px;">Client</div>
|
||||
<div style="color: #333;">${license.customer?.name || 'N/A'} (${license.customer?.email || 'N/A'})</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase; margin-bottom: 5px;">Hostname</div>
|
||||
<div style="color: #333; font-family: monospace;">${license.hostname || currentHostname}</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase; margin-bottom: 5px;">Limites</div>
|
||||
<div style="color: #333;">Apps: <strong>${maxApps}</strong> | Pushes: <strong>${maxPushes}</strong></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div style="color: #666; font-size: 11px; text-transform: uppercase; margin-bottom: 8px;">Fonctionnalités</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 4px;">${features || '<span style="color: #999;">Aucune</span>'}</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button onclick="showLicenseModal(null, null, '${currentHostname}')" style="
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
">🔄 Changer de licence</button>
|
||||
<button onclick="this.closest('#license-details-modal').remove()" style="
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
// Calculer les jours restants
|
||||
const now = new Date();
|
||||
const daysRemaining = Math.ceil((expirationDate - now) / (1000 * 60 * 60 * 24));
|
||||
// ============================================
|
||||
// MODAL D'UPLOAD DE LICENCE
|
||||
// ============================================
|
||||
|
||||
console.log("Trial created:", createdDate);
|
||||
console.log("Trial expires:", expirationDate);
|
||||
console.log("Days remaining:", daysRemaining);
|
||||
function showLicenseModal(error = null, errorCode = null, hostname = null) {
|
||||
console.log("Showing license modal", { error, errorCode, hostname });
|
||||
|
||||
return Math.max(0, daysRemaining);
|
||||
}
|
||||
// Supprimer l'ancien modal s'il existe
|
||||
hideLicenseModal();
|
||||
const detailsModal = document.getElementById('license-details-modal');
|
||||
if (detailsModal) detailsModal.remove();
|
||||
|
||||
function showLicenseModal() {
|
||||
console.log("Showing license modal");
|
||||
|
||||
// Créer le modal HTML
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'license-modal';
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.style.cssText = `
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
`;
|
||||
|
||||
content.innerHTML = `
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="font-size: 32px; margin: 0 0 10px 0; color: #333;">🔐 Git Pusher</h1>
|
||||
<p style="color: #666; margin: 0; font-size: 14px;">Activation de licence requise</p>
|
||||
</div>
|
||||
|
||||
<div style="background: #f5f7ff; padding: 15px; border-radius: 8px; margin-bottom: 25px; border-left: 4px solid #667eea;">
|
||||
<p style="margin: 0; color: #667eea; font-weight: 500; font-size: 13px;">
|
||||
📋 <strong>Hostname détecté:</strong> <span id="detected-hostname">Chargement...</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label style="display: block; font-weight: 600; color: #333; margin-bottom: 8px;">
|
||||
Entrez votre clé de licence
|
||||
</label>
|
||||
<textarea id="license-input" placeholder="Collez votre clé de licence ici..." style="
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'license-modal';
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
box-sizing: border-box;
|
||||
"></textarea>
|
||||
<small style="color: #999; display: block; margin-top: 8px;">
|
||||
Vous n'avez pas de licence? <a href="#" onclick="showGeneratorInfo(); return false;" style="color: #667eea; text-decoration: none;">Cliquez ici</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div id="license-message" style="display: none; padding: 12px; border-radius: 8px; margin-bottom: 20px; font-size: 14px;"></div>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button onclick="validateLicenseInput()" style="
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
">Activer la licence</button>
|
||||
<button onclick="skipLicense()" style="
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
background: #f5f7ff;
|
||||
color: #667eea;
|
||||
border: 2px solid #667eea;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
">Essai gratuit (7 jours)</button>
|
||||
</div>
|
||||
`;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
`;
|
||||
|
||||
// Message d'erreur si présent
|
||||
let errorHtml = '';
|
||||
if (error) {
|
||||
let errorStyle = 'background: #ffebee; color: #c62828; border-left: 4px solid #f44336;';
|
||||
if (errorCode === 'NO_LICENSE') {
|
||||
errorStyle = 'background: #fff3e0; color: #e65100; border-left: 4px solid #ff9800;';
|
||||
}
|
||||
errorHtml = `
|
||||
<div style="${errorStyle} padding: 12px; border-radius: 4px; margin-bottom: 20px; font-size: 13px;">
|
||||
${error}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
modal.appendChild(content);
|
||||
document.body.appendChild(modal);
|
||||
modal.innerHTML = `
|
||||
<div style="background: white; border-radius: 16px; padding: 40px; max-width: 550px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);">
|
||||
<div style="text-align: center; margin-bottom: 25px;">
|
||||
<h1 style="font-size: 28px; margin: 0 0 8px 0; color: #333;">🔐 Git Pusher</h1>
|
||||
<p style="color: #666; margin: 0; font-size: 14px;">Activation de licence requise</p>
|
||||
</div>
|
||||
|
||||
${errorHtml}
|
||||
|
||||
<div style="background: #e8f5e9; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #4caf50;">
|
||||
<p style="margin: 0; color: #2e7d32; font-size: 13px;">
|
||||
<strong>📋 Hostname Splunk:</strong><br>
|
||||
<code style="background: white; padding: 4px 8px; border-radius: 4px; font-size: 14px; display: inline-block; margin-top: 5px;">${hostname || 'Chargement...'}</code>
|
||||
</p>
|
||||
<p style="margin: 10px 0 0 0; color: #558b2f; font-size: 11px;">
|
||||
Communiquez ce hostname pour obtenir votre licence.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label style="display: block; font-weight: 600; color: #333; margin-bottom: 10px;">
|
||||
📄 Fichier de licence (.lic)
|
||||
</label>
|
||||
|
||||
<div id="license-dropzone" style="
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: #fafafa;
|
||||
" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)" onclick="document.getElementById('license-file-input').click()">
|
||||
<div style="font-size: 40px; margin-bottom: 10px;">📁</div>
|
||||
<div style="color: #666; font-size: 14px;">
|
||||
Glissez votre fichier <strong>.lic</strong> ici<br>
|
||||
<span style="color: #999; font-size: 12px;">ou cliquez pour sélectionner</span>
|
||||
</div>
|
||||
<input type="file" id="license-file-input" accept=".lic" style="display: none;" onchange="handleFileSelect(event)">
|
||||
</div>
|
||||
|
||||
<div id="selected-file-info" style="display: none; margin-top: 10px; padding: 10px; background: #e3f2fd; border-radius: 6px;">
|
||||
<span id="selected-file-name" style="color: #1565c0; font-weight: 500;"></span>
|
||||
<button onclick="clearSelectedFile()" style="float: right; background: none; border: none; color: #f44336; cursor: pointer;">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="license-message" style="display: none; padding: 12px; border-radius: 8px; margin-bottom: 20px; font-size: 14px;"></div>
|
||||
|
||||
<button id="activate-btn" onclick="uploadLicense()" disabled style="
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0.5;
|
||||
">Activer la licence</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<a href="#" onclick="showContactInfo(); return false;" style="color: #667eea; text-decoration: none; font-size: 13px;">
|
||||
Besoin d'une licence ? Contactez-nous
|
||||
</a>
|
||||
</div>
|
||||
|
||||
${currentLicense ? `
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<button onclick="hideLicenseModal()" style="background: none; border: none; color: #999; cursor: pointer; font-size: 13px;">
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Récupérer le hostname si pas fourni
|
||||
if (!hostname) {
|
||||
fetch(`${LICENSE_API_URL}/license/hostname`)
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const codeEl = modal.querySelector('code');
|
||||
if (codeEl) codeEl.textContent = d.hostname || 'Inconnu';
|
||||
currentHostname = d.hostname;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher le hostname
|
||||
getHostname().then(hostname => {
|
||||
document.getElementById('detected-hostname').textContent = hostname;
|
||||
});
|
||||
function hideLicenseModal() {
|
||||
const modal = document.getElementById('license-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
|
||||
function showGeneratorInfo() {
|
||||
alert(`Pour générer une clé de licence, exécutez sur le serveur Splunk:
|
||||
// ============================================
|
||||
// GESTION DU FICHIER
|
||||
// ============================================
|
||||
|
||||
python /opt/splunk/etc/apps/pusher_app/bin/license_generator.py
|
||||
let selectedLicenseContent = null;
|
||||
|
||||
Cela générera une clé basée sur votre hostname.`);
|
||||
function handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.currentTarget.style.borderColor = '#667eea';
|
||||
event.currentTarget.style.background = '#f0f4ff';
|
||||
}
|
||||
|
||||
function getHostname() {
|
||||
return new Promise((resolve) => {
|
||||
fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const hostname = d.entry?.[0]?.content?.host || 'unknown';
|
||||
resolve(hostname);
|
||||
})
|
||||
.catch(() => resolve('unknown'));
|
||||
});
|
||||
function handleDragLeave(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.currentTarget.style.borderColor = '#ccc';
|
||||
event.currentTarget.style.background = '#fafafa';
|
||||
}
|
||||
|
||||
function validateLicenseInput() {
|
||||
const licenseInput = document.getElementById('license-input').value.trim();
|
||||
|
||||
if (!licenseInput) {
|
||||
showLicenseMessage('Veuillez entrer une clé de licence', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher le message de chargement
|
||||
showLicenseMessage('Validation en cours...', 'info');
|
||||
|
||||
// Simuler la validation (en production, faire un appel à un serveur)
|
||||
// Pour l'instant, on accepte juste n'importe quelle licence
|
||||
if (licenseInput.length > 20) {
|
||||
// Stocker la licence
|
||||
setCookie(LICENSE_STORAGE_KEY, licenseInput, 365);
|
||||
|
||||
showLicenseMessage('✓ Licence activée avec succès!', 'success');
|
||||
|
||||
setTimeout(() => {
|
||||
closeLicenseModal();
|
||||
// Afficher les infos de licence
|
||||
displayLicenseInfo(licenseInput);
|
||||
}, 1500);
|
||||
} else {
|
||||
showLicenseMessage('Format de licence invalide', 'error');
|
||||
}
|
||||
}
|
||||
function handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.currentTarget.style.borderColor = '#ccc';
|
||||
event.currentTarget.style.background = '#fafafa';
|
||||
|
||||
function skipLicense() {
|
||||
// Créer une licence d'essai avec timestamp (format: TRIAL-timestamp)
|
||||
const trialLicense = 'TRIAL-' + Date.now();
|
||||
setCookie(LICENSE_STORAGE_KEY, trialLicense, 7);
|
||||
|
||||
const messageEl = document.getElementById('license-message');
|
||||
messageEl.style.display = 'block';
|
||||
messageEl.style.background = '#fff3cd';
|
||||
messageEl.style.color = '#856404';
|
||||
messageEl.style.border = '1px solid #ffeaa7';
|
||||
messageEl.textContent = '⏱️ Mode essai activé pour 7 jours';
|
||||
|
||||
setTimeout(() => {
|
||||
closeLicenseModal();
|
||||
// Afficher les infos de licence
|
||||
displayLicenseInfo(trialLicense);
|
||||
}, 1500);
|
||||
const files = event.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
processFile(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function showLicenseMessage(message, type) {
|
||||
const messageEl = document.getElementById('license-message');
|
||||
messageEl.style.display = 'block';
|
||||
messageEl.textContent = message;
|
||||
|
||||
if (type === 'success') {
|
||||
messageEl.style.background = '#d4edda';
|
||||
messageEl.style.color = '#155724';
|
||||
messageEl.style.border = '1px solid #c3e6cb';
|
||||
} else if (type === 'error') {
|
||||
messageEl.style.background = '#f8d7da';
|
||||
messageEl.style.color = '#721c24';
|
||||
messageEl.style.border = '1px solid #f5c6cb';
|
||||
} else if (type === 'info') {
|
||||
messageEl.style.background = '#d1ecf1';
|
||||
messageEl.style.color = '#0c5460';
|
||||
messageEl.style.border = '1px solid #bee5eb';
|
||||
}
|
||||
function handleFileSelect(event) {
|
||||
const files = event.target.files;
|
||||
if (files.length > 0) {
|
||||
processFile(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function validateStoredLicense(license) {
|
||||
console.log("Validating stored license...");
|
||||
function processFile(file) {
|
||||
console.log("Processing file:", file.name);
|
||||
|
||||
// Pour l'instant, accepter simplement la licence stockée
|
||||
// En production, faire une validation serveur
|
||||
if (license && license.length > 5) {
|
||||
console.log("License is valid");
|
||||
return true;
|
||||
}
|
||||
if (!file.name.endsWith('.lic')) {
|
||||
showLicenseMessage('Veuillez sélectionner un fichier .lic', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Si invalide, afficher le modal à nouveau
|
||||
showLicenseModal();
|
||||
return false;
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
selectedLicenseContent = e.target.result;
|
||||
|
||||
// Afficher le nom du fichier
|
||||
document.getElementById('selected-file-info').style.display = 'block';
|
||||
document.getElementById('selected-file-name').textContent = '📄 ' + file.name;
|
||||
|
||||
// Activer le bouton
|
||||
const btn = document.getElementById('activate-btn');
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
btn.style.cursor = 'pointer';
|
||||
|
||||
showLicenseMessage('Fichier prêt à être activé', 'info');
|
||||
};
|
||||
reader.onerror = function() {
|
||||
showLicenseMessage('Erreur de lecture du fichier', 'error');
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function closeLicenseModal() {
|
||||
const modal = document.getElementById('license-modal');
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
}
|
||||
function clearSelectedFile() {
|
||||
selectedLicenseContent = null;
|
||||
document.getElementById('selected-file-info').style.display = 'none';
|
||||
document.getElementById('license-file-input').value = '';
|
||||
|
||||
function checkLicenseBeforePush() {
|
||||
const license = getCookie(LICENSE_STORAGE_KEY);
|
||||
const btn = document.getElementById('activate-btn');
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.5';
|
||||
|
||||
if (!license) {
|
||||
alert('Veuillez d\'abord activer une licence');
|
||||
showLicenseModal();
|
||||
return false;
|
||||
}
|
||||
const msgEl = document.getElementById('license-message');
|
||||
msgEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Vérifier si c'est une licence d'essai expirée
|
||||
if (license.startsWith('TRIAL-')) {
|
||||
// À implémenter : vérifier la date
|
||||
}
|
||||
// ============================================
|
||||
// UPLOAD ET ACTIVATION
|
||||
// ============================================
|
||||
|
||||
return true;
|
||||
async function uploadLicense() {
|
||||
if (!selectedLicenseContent) {
|
||||
showLicenseMessage('Veuillez sélectionner un fichier de licence', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLicenseMessage('⏳ Validation en cours...', 'info');
|
||||
|
||||
const btn = document.getElementById('activate-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Validation...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${LICENSE_API_URL}/license/upload`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
license_content: selectedLicenseContent
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Upload result:", result);
|
||||
|
||||
if (result.success) {
|
||||
showLicenseMessage('✓ Licence activée avec succès!', 'success');
|
||||
currentLicense = result.license_info;
|
||||
|
||||
setTimeout(() => {
|
||||
hideLicenseModal();
|
||||
displayLicenseInfo(result.license_info);
|
||||
}, 1500);
|
||||
} else {
|
||||
showLicenseMessage(result.error || 'Erreur d\'activation', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activer la licence';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Upload error:", error);
|
||||
showLicenseMessage('Erreur de connexion au serveur', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Activer la licence';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// FONCTIONS UTILITAIRES DE COOKIE
|
||||
// UTILITAIRES
|
||||
// ============================================
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = "expires=" + d.toUTCString();
|
||||
document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/";
|
||||
console.log("Cookie set: " + name);
|
||||
function showLicenseMessage(message, type) {
|
||||
const msgEl = document.getElementById('license-message');
|
||||
if (!msgEl) return;
|
||||
|
||||
msgEl.style.display = 'block';
|
||||
msgEl.textContent = message;
|
||||
|
||||
switch (type) {
|
||||
case 'success':
|
||||
msgEl.style.background = '#e8f5e9';
|
||||
msgEl.style.color = '#2e7d32';
|
||||
msgEl.style.border = '1px solid #a5d6a7';
|
||||
break;
|
||||
case 'error':
|
||||
msgEl.style.background = '#ffebee';
|
||||
msgEl.style.color = '#c62828';
|
||||
msgEl.style.border = '1px solid #ef9a9a';
|
||||
break;
|
||||
case 'info':
|
||||
msgEl.style.background = '#e3f2fd';
|
||||
msgEl.style.color = '#1565c0';
|
||||
msgEl.style.border = '1px solid #90caf9';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i].trim();
|
||||
if (c.indexOf(nameEQ) === 0) {
|
||||
return decodeURIComponent(c.substring(nameEQ.length));
|
||||
}
|
||||
}
|
||||
return "";
|
||||
function showContactInfo() {
|
||||
alert(`Pour obtenir une licence Git Pusher:
|
||||
|
||||
1. Copiez votre hostname Splunk affiché ci-dessus
|
||||
2. Contactez-nous avec:
|
||||
- Votre hostname
|
||||
- Votre email
|
||||
- Le type de licence souhaité
|
||||
|
||||
Email: support@gitpusher.com
|
||||
Site: https://gitpusher.com`);
|
||||
}
|
||||
|
||||
function deleteCookie(name) {
|
||||
setCookie(name, "", -1);
|
||||
console.log("Cookie deleted: " + name);
|
||||
// ============================================
|
||||
// VÉRIFICATION AVANT PUSH
|
||||
// ============================================
|
||||
|
||||
async function checkLicenseBeforePush() {
|
||||
try {
|
||||
const response = await fetch(`${LICENSE_API_URL}/license/status`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status !== 'valid') {
|
||||
alert('⚠️ ' + (data.error || 'Licence invalide ou absente'));
|
||||
showLicenseModal(data.error, data.error_code, data.hostname);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier les limites
|
||||
const limits = data.license?.limits || {};
|
||||
const usage = data.usage || {};
|
||||
|
||||
if (limits.max_pushes_per_day > 0 && usage.pushes_today >= limits.max_pushes_per_day) {
|
||||
alert(`⚠️ Limite quotidienne atteinte (${limits.max_pushes_per_day} pushes/jour)`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("License check error:", error);
|
||||
alert('⚠️ Impossible de vérifier la licence');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// EXPORT POUR UTILISATION EXTERNE
|
||||
// ============================================
|
||||
|
||||
window.LicenseManager = {
|
||||
init: initializeLicense,
|
||||
check: checkLicenseBeforePush,
|
||||
showModal: showLicenseModal,
|
||||
getLicense: () => currentLicense,
|
||||
getHostname: () => currentHostname
|
||||
};
|
||||
|
||||
Binary file not shown.
@ -1 +1 @@
|
||||
919562
|
||||
212462
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Endpoints
|
||||
Endpoints REST pour gérer les licences
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
# Ajouter le chemin du module
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
|
||||
from license_validator import LicenseValidator, get_license_status, LICENSE_FILE_PATH
|
||||
|
||||
class LicenseHandler(BaseHTTPRequestHandler):
|
||||
"""Handler pour les requêtes de licence"""
|
||||
|
||||
def do_OPTIONS(self):
|
||||
"""Gérer les requêtes OPTIONS (CORS preflight)"""
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
"""Gérer les requêtes GET"""
|
||||
if self.path == '/custom/git_pusher/license_status':
|
||||
self.handle_license_status()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_POST(self):
|
||||
"""Gérer les requêtes POST"""
|
||||
if self.path == '/custom/git_pusher/install_license':
|
||||
self.handle_install_license()
|
||||
elif self.path == '/custom/git_pusher/request_trial':
|
||||
self.handle_request_trial()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def send_headers(self):
|
||||
"""Envoyer les headers CORS"""
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
|
||||
def handle_license_status(self):
|
||||
"""Vérifier le statut de la licence"""
|
||||
try:
|
||||
status = get_license_status()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps(status).encode())
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'licensed': False,
|
||||
'errors': [f'Erreur serveur: {str(e)}']
|
||||
}).encode())
|
||||
|
||||
def handle_install_license(self):
|
||||
"""Installer un fichier de licence uploadé"""
|
||||
try:
|
||||
# Lire le body
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
data = json.loads(body.decode())
|
||||
|
||||
license_content = data.get('license_file')
|
||||
filename = data.get('filename', 'uploaded.lic')
|
||||
|
||||
if not license_content:
|
||||
raise Exception("Aucun contenu de licence fourni")
|
||||
|
||||
# Créer un fichier temporaire
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.lic', delete=False) as temp_file:
|
||||
temp_file.write(license_content)
|
||||
temp_path = temp_file.name
|
||||
|
||||
try:
|
||||
# Valider et installer
|
||||
validator = LicenseValidator()
|
||||
result = validator.install_license(temp_path, LICENSE_FILE_PATH)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps(result).encode())
|
||||
|
||||
finally:
|
||||
# Nettoyer le fichier temporaire
|
||||
if os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(400)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': False,
|
||||
'message': f'Erreur: {str(e)}'
|
||||
}).encode())
|
||||
|
||||
def handle_request_trial(self):
|
||||
"""Créer une licence d'essai"""
|
||||
try:
|
||||
from datetime import datetime, timedelta
|
||||
import socket
|
||||
import hashlib
|
||||
|
||||
# Générer une licence d'essai basique (7 jours)
|
||||
hostname = socket.gethostname()
|
||||
trial_license = {
|
||||
"license": {
|
||||
"version": "1.0",
|
||||
"license_id": "TRIAL-" + hashlib.md5(
|
||||
f"{hostname}{datetime.now()}".encode()
|
||||
).hexdigest()[:8].upper(),
|
||||
"customer": {
|
||||
"name": "Trial User",
|
||||
"hostname": hostname
|
||||
},
|
||||
"validity": {
|
||||
"issued": datetime.now().isoformat(),
|
||||
"expires": (datetime.now() + timedelta(days=7)).isoformat(),
|
||||
"days": 7
|
||||
},
|
||||
"limits": {
|
||||
"max_pushes": 50,
|
||||
"max_apps": None
|
||||
},
|
||||
"features": {
|
||||
"git_push": True,
|
||||
"multi_branch": False,
|
||||
"auto_commit": False,
|
||||
"webhooks": False
|
||||
},
|
||||
"type": "trial"
|
||||
},
|
||||
"signature": "TRIAL-NO-SIGNATURE",
|
||||
"checksum": "TRIAL"
|
||||
}
|
||||
|
||||
# Créer le dossier si nécessaire
|
||||
os.makedirs(os.path.dirname(LICENSE_FILE_PATH), exist_ok=True)
|
||||
|
||||
# Sauvegarder
|
||||
with open(LICENSE_FILE_PATH, 'w') as f:
|
||||
json.dump(trial_license, f, indent=2)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': True,
|
||||
'message': 'Licence d\'essai créée (7 jours, 50 pushes max)',
|
||||
'license_info': {
|
||||
'license_id': trial_license['license']['license_id'],
|
||||
'type': 'trial',
|
||||
'expires': trial_license['license']['validity']['expires'],
|
||||
'days_remaining': 7
|
||||
}
|
||||
}).encode())
|
||||
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_headers()
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(json.dumps({
|
||||
'success': False,
|
||||
'message': f'Erreur: {str(e)}'
|
||||
}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Override pour éviter les logs HTTP par défaut"""
|
||||
pass
|
||||
@ -1,173 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Generator
|
||||
Génère des clés de licence basées sur le hostname Splunk
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Secret key pour générer les licences (À CHANGER !)
|
||||
SECRET_KEY = "git_pusher_license_secret_2024"
|
||||
|
||||
def get_hostname():
|
||||
"""Récupérer le hostname du serveur"""
|
||||
return socket.gethostname()
|
||||
|
||||
def generate_license(hostname, days_valid=365, max_pushes=None):
|
||||
"""
|
||||
Générer une clé de licence
|
||||
|
||||
Args:
|
||||
hostname: nom d'hôte Splunk
|
||||
days_valid: nombre de jours de validité
|
||||
max_pushes: nombre maximum de pushes (None = illimité)
|
||||
|
||||
Returns:
|
||||
license_key: clé de licence formatée
|
||||
"""
|
||||
|
||||
# Créer la date d'expiration
|
||||
expiration_date = datetime.now() + timedelta(days=days_valid)
|
||||
expiration_str = expiration_date.strftime("%Y-%m-%d")
|
||||
|
||||
# Créer le payload
|
||||
payload = {
|
||||
"hostname": hostname,
|
||||
"expiration": expiration_str,
|
||||
"max_pushes": max_pushes,
|
||||
"issued": datetime.now().strftime("%Y-%m-%d")
|
||||
}
|
||||
|
||||
# Convertir en JSON et encoder en base64
|
||||
payload_json = json.dumps(payload, separators=(',', ':'))
|
||||
payload_b64 = base64.b64encode(payload_json.encode()).decode()
|
||||
|
||||
# Créer la signature HMAC
|
||||
signature = hmac.new(
|
||||
SECRET_KEY.encode(),
|
||||
payload_b64.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()[:16] # Prendre les 16 premiers caractères
|
||||
|
||||
# Formater la clé de licence
|
||||
license_key = f"{signature}-{payload_b64}"
|
||||
|
||||
return license_key, payload
|
||||
|
||||
def validate_license(license_key, hostname):
|
||||
"""
|
||||
Valider une clé de licence
|
||||
|
||||
Args:
|
||||
license_key: clé à valider
|
||||
hostname: hostname Splunk actuel
|
||||
|
||||
Returns:
|
||||
dict: {valid: bool, error: str, expiration: str, max_pushes: int}
|
||||
"""
|
||||
|
||||
try:
|
||||
# Séparer signature et payload
|
||||
parts = license_key.split('-', 1)
|
||||
if len(parts) != 2:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': 'Format de clé invalide'
|
||||
}
|
||||
|
||||
signature, payload_b64 = parts
|
||||
|
||||
# Vérifier la signature
|
||||
expected_signature = hmac.new(
|
||||
SECRET_KEY.encode(),
|
||||
payload_b64.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()[:16]
|
||||
|
||||
if signature != expected_signature:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': 'Signature invalide - clé corrompue ou falsifiée'
|
||||
}
|
||||
|
||||
# Décoder le payload
|
||||
try:
|
||||
payload_json = base64.b64decode(payload_b64).decode()
|
||||
payload = json.loads(payload_json)
|
||||
except Exception as e:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Erreur de décodage: {str(e)}'
|
||||
}
|
||||
|
||||
# Vérifier le hostname
|
||||
if payload.get('hostname') != hostname:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Cette licence est pour {payload.get("hostname")}, pas {hostname}'
|
||||
}
|
||||
|
||||
# Vérifier l'expiration
|
||||
expiration = datetime.strptime(payload.get('expiration'), '%Y-%m-%d')
|
||||
if datetime.now() > expiration:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Licence expirée le {payload.get("expiration")}'
|
||||
}
|
||||
|
||||
return {
|
||||
'valid': True,
|
||||
'expiration': payload.get('expiration'),
|
||||
'max_pushes': payload.get('max_pushes'),
|
||||
'days_remaining': (expiration - datetime.now()).days
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'valid': False,
|
||||
'error': f'Erreur de validation: {str(e)}'
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
hostname = get_hostname()
|
||||
print("=" * 60)
|
||||
print("Git Pusher - License Generator")
|
||||
print("=" * 60)
|
||||
print(f"\nHostname détecté: {hostname}")
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'validate':
|
||||
# Mode validation
|
||||
license_key = sys.argv[2] if len(sys.argv) > 2 else input("Entrez la clé de licence: ")
|
||||
result = validate_license(license_key, hostname)
|
||||
|
||||
print("\nRésultat de validation:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
# Mode génération
|
||||
days = int(sys.argv[1]) if len(sys.argv) > 1 else 365
|
||||
max_pushes = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
||||
|
||||
license_key, payload = generate_license(hostname, days, max_pushes)
|
||||
|
||||
print(f"\n📋 Payload:")
|
||||
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
||||
|
||||
print(f"\n🔑 Clé de licence générée:")
|
||||
print(license_key)
|
||||
|
||||
print(f"\n✓ Valide pour: {days} jours")
|
||||
if max_pushes:
|
||||
print(f"✓ Pushes limités à: {max_pushes}")
|
||||
|
||||
# Tester la validation
|
||||
print(f"\n✔️ Test de validation:")
|
||||
result = validate_license(license_key, hostname)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Pusher - License Validator (Server-side)
|
||||
Ce fichier doit être déployé sur le serveur Splunk dans l'application
|
||||
|
||||
Emplacement: /opt/splunk/etc/apps/pusher_app_prem/bin/license_validator.py
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import logging
|
||||
|
||||
# Configuration du logging
|
||||
log_dir = '/opt/splunk/var/log/splunk'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(log_dir, 'license_validator.log')),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger('license_validator')
|
||||
|
||||
# ============================================
|
||||
# CONFIGURATION - DOIT CORRESPONDRE AU GÉNÉRATEUR !
|
||||
# ============================================
|
||||
|
||||
# IMPORTANT: Cette clé DOIT être identique à celle du générateur
|
||||
SECRET_KEY = "git_pusher_super_secret_key_2024_change_me_in_production"
|
||||
|
||||
# Chemin vers le fichier de licence
|
||||
LICENSE_FILE_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/license.lic"
|
||||
|
||||
# Fichier de cache pour les stats d'utilisation
|
||||
USAGE_STATS_PATH = "/opt/splunk/etc/apps/pusher_app_prem/local/usage_stats.json"
|
||||
|
||||
|
||||
def get_splunk_hostname():
|
||||
"""Récupérer le hostname du serveur Splunk"""
|
||||
# Essayer d'abord via l'API Splunk
|
||||
try:
|
||||
import urllib.request
|
||||
import ssl
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
splunk_username = os.environ.get('SPLUNK_USERNAME', 'admin')
|
||||
splunk_password = os.environ.get('SPLUNK_PASSWORD', '2312Jocpam!?')
|
||||
credentials = base64.b64encode(f"{splunk_username}:{splunk_password}".encode()).decode()
|
||||
|
||||
req = urllib.request.Request("https://127.0.0.1:8089/services/server/info?output_mode=json")
|
||||
req.add_header('Authorization', f'Basic {credentials}')
|
||||
|
||||
with urllib.request.urlopen(req, timeout=5, context=ssl_context) as response:
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
hostname = data.get('entry', [{}])[0].get('content', {}).get('host', '')
|
||||
if hostname:
|
||||
return hostname.lower().strip()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get hostname via Splunk API: {e}")
|
||||
|
||||
# Fallback sur le hostname système
|
||||
return socket.gethostname().lower().strip()
|
||||
|
||||
|
||||
def create_signature(data_str):
|
||||
"""Créer une signature HMAC-SHA256"""
|
||||
signature = hmac.new(
|
||||
SECRET_KEY.encode('utf-8'),
|
||||
data_str.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
return signature
|
||||
|
||||
|
||||
def verify_signature(data_str, signature):
|
||||
"""Vérifier une signature"""
|
||||
expected = create_signature(data_str)
|
||||
return hmac.compare_digest(expected, signature)
|
||||
|
||||
|
||||
def read_license_file(filepath=None):
|
||||
"""Lire et parser un fichier de licence"""
|
||||
if filepath is None:
|
||||
filepath = LICENSE_FILE_PATH
|
||||
|
||||
try:
|
||||
if not os.path.exists(filepath):
|
||||
return None
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extraire la partie base64 (ignorer les commentaires)
|
||||
lines = content.strip().split('\n')
|
||||
base64_lines = [l for l in lines if not l.startswith('#') and l.strip()]
|
||||
base64_content = ''.join(base64_lines)
|
||||
|
||||
# Décoder
|
||||
decoded = base64.b64decode(base64_content).decode('utf-8')
|
||||
license_file = json.loads(decoded)
|
||||
|
||||
return license_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading license file: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def parse_license_content(content):
|
||||
"""Parser le contenu d'un fichier de licence (pour l'upload)"""
|
||||
try:
|
||||
# Extraire la partie base64 (ignorer les commentaires)
|
||||
lines = content.strip().split('\n')
|
||||
base64_lines = [l for l in lines if not l.startswith('#') and l.strip()]
|
||||
base64_content = ''.join(base64_lines)
|
||||
|
||||
# Décoder
|
||||
decoded = base64.b64decode(base64_content).decode('utf-8')
|
||||
license_file = json.loads(decoded)
|
||||
|
||||
return license_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing license content: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def validate_license(license_file=None, current_hostname=None):
|
||||
"""
|
||||
Valider une licence
|
||||
|
||||
Args:
|
||||
license_file: Contenu du fichier de licence (dict) - si None, lit le fichier par défaut
|
||||
current_hostname: Hostname actuel - si None, détecte automatiquement
|
||||
|
||||
Returns:
|
||||
dict avec {valid: bool, error: str, license_info: dict}
|
||||
"""
|
||||
try:
|
||||
# Lire la licence si non fournie
|
||||
if license_file is None:
|
||||
license_file = read_license_file()
|
||||
if license_file is None:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": "Aucun fichier de licence trouvé",
|
||||
"error_code": "NO_LICENSE"
|
||||
}
|
||||
|
||||
# Récupérer le hostname si non fourni
|
||||
if current_hostname is None:
|
||||
current_hostname = get_splunk_hostname()
|
||||
|
||||
license_data = license_file.get("license", {})
|
||||
signature = license_file.get("signature", "")
|
||||
|
||||
# Recréer le JSON pour vérifier la signature
|
||||
license_json = json.dumps(license_data, separators=(',', ':'), sort_keys=True)
|
||||
|
||||
# Vérifier la signature
|
||||
if not verify_signature(license_json, signature):
|
||||
return {
|
||||
"valid": False,
|
||||
"error": "Signature invalide - fichier corrompu ou modifié",
|
||||
"error_code": "INVALID_SIGNATURE"
|
||||
}
|
||||
|
||||
# Vérifier le hostname
|
||||
expected_hostname = license_data.get("binding", {}).get("hostname", "").lower()
|
||||
current_hostname_clean = current_hostname.lower().strip()
|
||||
|
||||
if current_hostname_clean != expected_hostname:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Cette licence est pour '{expected_hostname}', pas '{current_hostname_clean}'",
|
||||
"error_code": "HOSTNAME_MISMATCH",
|
||||
"expected_hostname": expected_hostname,
|
||||
"current_hostname": current_hostname_clean
|
||||
}
|
||||
|
||||
# Vérifier l'expiration
|
||||
expires_timestamp = license_data.get("dates", {}).get("expires_timestamp", 0)
|
||||
if datetime.now().timestamp() > expires_timestamp:
|
||||
expires_date = license_data.get("dates", {}).get("expires", "unknown")
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Licence expirée le {expires_date}",
|
||||
"error_code": "EXPIRED"
|
||||
}
|
||||
|
||||
# Calculer les jours restants
|
||||
days_remaining = (expires_timestamp - datetime.now().timestamp()) / (24 * 3600)
|
||||
|
||||
return {
|
||||
"valid": True,
|
||||
"license_id": license_data.get("license_id"),
|
||||
"type": license_data.get("type"),
|
||||
"type_name": license_data.get("type_name"),
|
||||
"customer": license_data.get("customer", {}),
|
||||
"expires": license_data.get("dates", {}).get("expires"),
|
||||
"days_remaining": int(days_remaining),
|
||||
"limits": license_data.get("limits", {}),
|
||||
"features": license_data.get("features", []),
|
||||
"hostname": expected_hostname
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Validation error: {e}")
|
||||
return {
|
||||
"valid": False,
|
||||
"error": f"Erreur de validation: {str(e)}",
|
||||
"error_code": "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
|
||||
def save_license_file(content):
|
||||
"""
|
||||
Sauvegarder un nouveau fichier de licence
|
||||
|
||||
Args:
|
||||
content: Contenu du fichier .lic
|
||||
|
||||
Returns:
|
||||
dict avec {success: bool, error: str}
|
||||
"""
|
||||
try:
|
||||
# Créer le dossier local si nécessaire
|
||||
local_dir = os.path.dirname(LICENSE_FILE_PATH)
|
||||
os.makedirs(local_dir, exist_ok=True)
|
||||
|
||||
# Parser d'abord pour valider
|
||||
license_file = parse_license_content(content)
|
||||
if license_file is None:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Format de fichier de licence invalide"
|
||||
}
|
||||
|
||||
# Valider la licence
|
||||
validation = validate_license(license_file)
|
||||
if not validation.get("valid"):
|
||||
return {
|
||||
"success": False,
|
||||
"error": validation.get("error", "Licence invalide")
|
||||
}
|
||||
|
||||
# Sauvegarder
|
||||
with open(LICENSE_FILE_PATH, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
logger.info(f"License saved: {validation.get('license_id')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"license_info": validation
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving license: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def get_usage_stats():
|
||||
"""Récupérer les statistiques d'utilisation"""
|
||||
try:
|
||||
if os.path.exists(USAGE_STATS_PATH):
|
||||
with open(USAGE_STATS_PATH, 'r') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
"pushes_today": 0,
|
||||
"pushes_total": 0,
|
||||
"last_push_date": None,
|
||||
"apps_pushed": []
|
||||
}
|
||||
|
||||
|
||||
def increment_usage():
|
||||
"""Incrémenter le compteur d'utilisation"""
|
||||
stats = get_usage_stats()
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Reset si nouveau jour
|
||||
if stats.get("last_push_date") != today:
|
||||
stats["pushes_today"] = 0
|
||||
stats["last_push_date"] = today
|
||||
|
||||
stats["pushes_today"] += 1
|
||||
stats["pushes_total"] += 1
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(USAGE_STATS_PATH), exist_ok=True)
|
||||
with open(USAGE_STATS_PATH, 'w') as f:
|
||||
json.dump(stats, f)
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving usage stats: {e}")
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def check_limits():
|
||||
"""
|
||||
Vérifier si les limites de la licence sont respectées
|
||||
|
||||
Returns:
|
||||
dict avec {allowed: bool, error: str}
|
||||
"""
|
||||
validation = validate_license()
|
||||
|
||||
if not validation.get("valid"):
|
||||
return {
|
||||
"allowed": False,
|
||||
"error": validation.get("error")
|
||||
}
|
||||
|
||||
limits = validation.get("limits", {})
|
||||
stats = get_usage_stats()
|
||||
|
||||
# Vérifier pushes par jour
|
||||
max_pushes = limits.get("max_pushes_per_day", -1)
|
||||
if max_pushes > 0 and stats.get("pushes_today", 0) >= max_pushes:
|
||||
return {
|
||||
"allowed": False,
|
||||
"error": f"Limite quotidienne atteinte ({max_pushes} pushes/jour)"
|
||||
}
|
||||
|
||||
return {
|
||||
"allowed": True,
|
||||
"license_info": validation,
|
||||
"usage": stats
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# API REST POUR L'INTERFACE WEB
|
||||
# ============================================
|
||||
|
||||
class LicenseAPIHandler(BaseHTTPRequestHandler):
|
||||
"""Handler pour les requêtes de l'API licence"""
|
||||
|
||||
def send_cors_headers(self):
|
||||
"""Envoyer les headers CORS complets"""
|
||||
origin = self.headers.get('Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Origin', origin)
|
||||
self.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
|
||||
self.send_header('Access-Control-Allow-Credentials', 'true')
|
||||
self.send_header('Access-Control-Max-Age', '86400')
|
||||
|
||||
def do_OPTIONS(self):
|
||||
logger.info(f"OPTIONS request from {self.headers.get('Origin', 'unknown')}")
|
||||
self.send_response(200)
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
def do_GET(self):
|
||||
"""GET /license - Récupérer les infos de licence"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
if '/license/status' in self.path or self.path == '/license':
|
||||
validation = validate_license()
|
||||
usage = get_usage_stats()
|
||||
hostname = get_splunk_hostname()
|
||||
|
||||
response = {
|
||||
"status": "valid" if validation.get("valid") else "invalid",
|
||||
"hostname": hostname,
|
||||
"license": validation if validation.get("valid") else None,
|
||||
"error": validation.get("error") if not validation.get("valid") else None,
|
||||
"error_code": validation.get("error_code") if not validation.get("valid") else None,
|
||||
"usage": usage
|
||||
}
|
||||
|
||||
elif '/license/hostname' in self.path:
|
||||
response = {
|
||||
"hostname": get_splunk_hostname()
|
||||
}
|
||||
|
||||
else:
|
||||
response = {"error": "Unknown endpoint"}
|
||||
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GET error: {e}")
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||
|
||||
def do_POST(self):
|
||||
"""POST /license/upload - Uploader une nouvelle licence"""
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_cors_headers()
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length).decode('utf-8')
|
||||
|
||||
if '/license/upload' in self.path:
|
||||
# Le body contient le contenu du fichier .lic
|
||||
data = json.loads(body)
|
||||
license_content = data.get('license_content', '')
|
||||
|
||||
if not license_content:
|
||||
response = {"success": False, "error": "Contenu de licence vide"}
|
||||
else:
|
||||
response = save_license_file(license_content)
|
||||
|
||||
elif '/license/delete' in self.path:
|
||||
# Supprimer la licence
|
||||
if os.path.exists(LICENSE_FILE_PATH):
|
||||
os.remove(LICENSE_FILE_PATH)
|
||||
response = {"success": True, "message": "Licence supprimée"}
|
||||
else:
|
||||
response = {"success": False, "error": "Aucune licence à supprimer"}
|
||||
|
||||
else:
|
||||
response = {"error": "Unknown endpoint"}
|
||||
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"POST error: {e}")
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
logger.debug(format % args)
|
||||
|
||||
|
||||
def start_license_server(port=9998):
|
||||
"""Démarrer le serveur API licence (optionnel, peut être intégré au serveur principal)"""
|
||||
server = HTTPServer(('127.0.0.1', port), LicenseAPIHandler)
|
||||
logger.info(f"License API server listening on 127.0.0.1:{port}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
# ============================================
|
||||
# CLI POUR TESTS
|
||||
# ============================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "status":
|
||||
result = validate_license()
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
elif sys.argv[1] == "hostname":
|
||||
print(f"Hostname: {get_splunk_hostname()}")
|
||||
|
||||
elif sys.argv[1] == "server":
|
||||
start_license_server()
|
||||
|
||||
else:
|
||||
print("Usage:")
|
||||
print(" python license_validator.py status # Vérifier la licence")
|
||||
print(" python license_validator.py hostname # Afficher le hostname")
|
||||
print(" python license_validator.py server # Démarrer l'API")
|
||||
else:
|
||||
result = validate_license()
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
@ -1,5 +1,249 @@
|
||||
#!/bin/bash
|
||||
export SPLUNK_USERNAME=admin
|
||||
export SPLUNK_PASSWORD='2312Jocpam!?'
|
||||
python3 /opt/splunk/etc/apps/pusher_app/bin/git_pusher.py > /opt/splunk/var/log/splunk/git_pusher_startup.log 2>&1 &
|
||||
echo $! > /opt/splunk/etc/apps/pusher_app/bin/git_pusher.pid
|
||||
# ============================================
|
||||
# Git Pusher - Start Script
|
||||
# Version 2.0 avec système de licence
|
||||
# ============================================
|
||||
|
||||
# Configuration
|
||||
SPLUNK_HOME=${SPLUNK_HOME:-/opt/splunk}
|
||||
APP_HOME="${SPLUNK_HOME}/etc/apps/pusher_app_prem"
|
||||
BIN_DIR="${APP_HOME}/bin"
|
||||
LOG_DIR="${SPLUNK_HOME}/var/log/splunk"
|
||||
PID_FILE="${BIN_DIR}/git_pusher.pid"
|
||||
|
||||
# Couleurs pour les logs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Fonction de logging
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Vérifier si le serveur est déjà en cours d'exécution
|
||||
check_running() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
return 0 # Running
|
||||
fi
|
||||
fi
|
||||
return 1 # Not running
|
||||
}
|
||||
|
||||
# Démarrer le serveur
|
||||
start_server() {
|
||||
log_info "Starting Git Pusher server..."
|
||||
|
||||
# Vérifier si déjà en cours
|
||||
if check_running; then
|
||||
log_warn "Git Pusher is already running (PID: $(cat $PID_FILE))"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Créer le répertoire de logs
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Variables d'environnement pour l'authentification Splunk
|
||||
# IMPORTANT: Modifiez ces valeurs ou utilisez des variables d'environnement
|
||||
export SPLUNK_USERNAME=${SPLUNK_USERNAME:-admin}
|
||||
export SPLUNK_PASSWORD=${SPLUNK_PASSWORD:-changeme}
|
||||
|
||||
# Démarrer le serveur Python
|
||||
cd "$BIN_DIR"
|
||||
python3 git_pusher.py > "${LOG_DIR}/git_pusher_startup.log" 2>&1 &
|
||||
|
||||
# Sauvegarder le PID
|
||||
echo $! > "$PID_FILE"
|
||||
|
||||
# Attendre un peu et vérifier
|
||||
sleep 2
|
||||
|
||||
if check_running; then
|
||||
log_info "Git Pusher started successfully (PID: $(cat $PID_FILE))"
|
||||
log_info "Server listening on port 9999"
|
||||
|
||||
# Vérifier le statut de la licence
|
||||
HOSTNAME=$(hostname)
|
||||
log_info "Hostname: $HOSTNAME"
|
||||
|
||||
if [ -f "${APP_HOME}/local/license.lic" ]; then
|
||||
log_info "License file found"
|
||||
else
|
||||
log_warn "No license file found at ${APP_HOME}/local/license.lic"
|
||||
log_warn "The application will require license activation"
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to start Git Pusher"
|
||||
log_error "Check logs at ${LOG_DIR}/git_pusher.log"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Arrêter le serveur
|
||||
stop_server() {
|
||||
log_info "Stopping Git Pusher server..."
|
||||
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
kill $PID
|
||||
sleep 2
|
||||
|
||||
# Force kill si nécessaire
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
log_warn "Force killing process..."
|
||||
kill -9 $PID
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
log_info "Git Pusher stopped"
|
||||
return 0
|
||||
else
|
||||
log_warn "Process not running, cleaning up PID file"
|
||||
rm -f "$PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
log_warn "PID file not found, Git Pusher may not be running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Redémarrer le serveur
|
||||
restart_server() {
|
||||
log_info "Restarting Git Pusher server..."
|
||||
stop_server
|
||||
sleep 1
|
||||
start_server
|
||||
}
|
||||
|
||||
# Afficher le statut
|
||||
show_status() {
|
||||
echo "============================================"
|
||||
echo "Git Pusher Status"
|
||||
echo "============================================"
|
||||
|
||||
if check_running; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
echo -e "Status: ${GREEN}RUNNING${NC}"
|
||||
echo "PID: $PID"
|
||||
echo "Port: 9999"
|
||||
else
|
||||
echo -e "Status: ${RED}STOPPED${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Paths:"
|
||||
echo " App Home: $APP_HOME"
|
||||
echo " Bin Dir: $BIN_DIR"
|
||||
echo " Log Dir: $LOG_DIR"
|
||||
echo ""
|
||||
|
||||
# Statut de la licence
|
||||
echo "License:"
|
||||
if [ -f "${APP_HOME}/local/license.lic" ]; then
|
||||
echo -e " File: ${GREEN}Present${NC}"
|
||||
# Essayer de lire quelques infos
|
||||
if command -v python3 &> /dev/null; then
|
||||
python3 -c "
|
||||
import sys
|
||||
sys.path.insert(0, '$BIN_DIR')
|
||||
from license_validator import validate_license
|
||||
result = validate_license()
|
||||
if result.get('valid'):
|
||||
print(f\" Type: {result.get('type_name', 'N/A')}\")
|
||||
print(f\" Expires: {result.get('expires', 'N/A')}\")
|
||||
print(f\" Days remaining: {result.get('days_remaining', 'N/A')}\")
|
||||
else:
|
||||
print(f\" Status: Invalid - {result.get('error', 'Unknown error')}\")
|
||||
" 2>/dev/null || echo " Unable to read license details"
|
||||
fi
|
||||
else
|
||||
echo -e " File: ${YELLOW}Not found${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
# Afficher les logs
|
||||
show_logs() {
|
||||
LOG_FILE="${LOG_DIR}/git_pusher.log"
|
||||
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
if [ "$1" == "-f" ]; then
|
||||
tail -f "$LOG_FILE"
|
||||
else
|
||||
tail -n 50 "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
log_warn "Log file not found at $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Menu d'aide
|
||||
show_help() {
|
||||
echo "Git Pusher - Server Management Script"
|
||||
echo ""
|
||||
echo "Usage: $0 {start|stop|restart|status|logs|help}"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start Start the Git Pusher server"
|
||||
echo " stop Stop the Git Pusher server"
|
||||
echo " restart Restart the Git Pusher server"
|
||||
echo " status Show the current status"
|
||||
echo " logs Show recent logs (use -f for follow)"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " SPLUNK_USERNAME Splunk admin username (default: admin)"
|
||||
echo " SPLUNK_PASSWORD Splunk admin password (default: changeme)"
|
||||
echo " SPLUNK_HOME Splunk installation directory (default: /opt/splunk)"
|
||||
}
|
||||
|
||||
# Main
|
||||
case "$1" in
|
||||
start)
|
||||
start_server
|
||||
;;
|
||||
stop)
|
||||
stop_server
|
||||
;;
|
||||
restart)
|
||||
restart_server
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
logs)
|
||||
show_logs "$2"
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
# Par défaut, démarrer le serveur (pour compatibilité)
|
||||
if [ -z "$1" ]; then
|
||||
start_server
|
||||
else
|
||||
echo "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -0,0 +1 @@
|
||||
{"pushes_today": 9, "pushes_total": 9, "last_push_date": "2026-01-31", "apps_pushed": []}
|
||||
Loading…
Reference in new issue