Compare commits

..
3 Commits
9 changed files with 125 additions and 26 deletions

No files matched your search

+13 -2
View File
@@ -88,8 +88,8 @@ if ! apt-get install -y asterisk; then
apt-get install -y -t sid asterisk
fi
# Sons Asterisk en français (exacts de la machine de dev)
apt-get install -y asterisk-core-sounds-fr asterisk-core-sounds-fr-wav asterisk-core-sounds-fr-gsm 2>/dev/null || true
# Sons Asterisk en français et anglais
apt-get install -y asterisk-core-sounds-fr asterisk-core-sounds-fr-wav asterisk-core-sounds-fr-gsm asterisk-core-sounds-en asterisk-core-sounds-en-gsm 2>/dev/null || true
# Nginx, PHP-FPM et extensions requises
apt-get install -y --no-install-recommends \
@@ -325,6 +325,17 @@ if ! grep -q 'voicemail.custom.conf' /etc/asterisk/voicemail.conf 2>/dev/null; t
fi
mkdir -p /var/spool/asterisk/voicemail/default /var/lib/asterisk/sounds/custom /var/lib/asterisk/moh
for lang in fr en es; do
if [ -d "/usr/share/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang" ]; then
ln -sf "/usr/share/asterisk/sounds/$lang" "/var/lib/asterisk/sounds/$lang"
fi
if [ -d "/var/lib/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom "/var/lib/asterisk/sounds/$lang/custom" 2>/dev/null || true
fi
done
if [ ! -e "/usr/share/asterisk/sounds/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom /usr/share/asterisk/sounds/custom 2>/dev/null || true
fi
chown -R asterisk:asterisk /etc/asterisk /var/spool/asterisk /var/lib/asterisk
chmod 0664 /etc/asterisk/*.conf /etc/asterisk/*.custom.conf 2>/dev/null || true
chmod 2750 /etc/asterisk/keys
+2 -2
View File
@@ -140,7 +140,7 @@ try {
$installedVerData = file_exists("{$appDir}/version.json")
? json_decode((string)file_get_contents("{$appDir}/version.json"), true)
: [];
$installedVer = $installedVerData['version'] ?? '1.4.6';
$installedVer = $installedVerData['version'] ?? '1.4.9';
echo json_encode([
'success' => true,
@@ -227,7 +227,7 @@ try {
@chmod($webLog, 0664);
}
@unlink('/tmp/nexus_fw_status_cache.json');
exec('sudo /usr/bin/fail2ban-client reload 2>&1');
exec('sudo /usr/bin/systemctl restart fail2ban 2>&1 || sudo /usr/bin/fail2ban-client reload 2>&1');
echo json_encode([
'success' => true,
+1 -1
View File
@@ -18,7 +18,7 @@ $companyLogo = trim($branding['company_logo'] ?? '');
$hasBranding = !empty($companyName) || !empty($companyLogo);
$appVerData = file_exists(__DIR__ . '/../version.json') ? json_decode((string)file_get_contents(__DIR__ . '/../version.json'), true) : [];
$appVersion = $appVerData['version'] ?? '1.4.6';
$appVersion = $appVerData['version'] ?? '1.4.9';
?>
<!DOCTYPE html>
<html lang="fr" class="h-full">
+11
View File
@@ -103,6 +103,17 @@ if ! grep -q 'voicemail.custom.conf' /etc/asterisk/voicemail.conf 2>/dev/null; t
fi
mkdir -p /var/lib/asterisk/sounds/custom /var/lib/asterisk/moh /var/spool/asterisk/voicemail/default 2>/dev/null || true
for lang in fr en es; do
if [ -d "/usr/share/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang" ]; then
ln -sf "/usr/share/asterisk/sounds/$lang" "/var/lib/asterisk/sounds/$lang" 2>/dev/null || true
fi
if [ -d "/var/lib/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom "/var/lib/asterisk/sounds/$lang/custom" 2>/dev/null || true
fi
done
if [ ! -e "/usr/share/asterisk/sounds/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom /usr/share/asterisk/sounds/custom 2>/dev/null || true
fi
chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/spool/asterisk /var/log/asterisk 2>/dev/null || true
chmod -R 775 /etc/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/spool/asterisk 2>/dev/null || true
chmod 2775 /etc/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/sounds/custom /var/lib/asterisk/moh /var/spool/asterisk /var/spool/asterisk/voicemail 2>/dev/null || true
+11
View File
@@ -136,6 +136,17 @@ require_once "/var/www/nexus-pbx/src/SystemChecker.php";
App\SystemChecker::ensureAsteriskIncludes();
' 2>/dev/null || true
mkdir -p /var/spool/asterisk/voicemail/default /var/lib/asterisk/sounds/custom /var/lib/asterisk/moh 2>/dev/null || true
for lang in fr en es; do
if [ -d "/usr/share/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang" ]; then
ln -sf "/usr/share/asterisk/sounds/$lang" "/var/lib/asterisk/sounds/$lang" 2>/dev/null || true
fi
if [ -d "/var/lib/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom "/var/lib/asterisk/sounds/$lang/custom" 2>/dev/null || true
fi
done
if [ ! -e "/usr/share/asterisk/sounds/custom" ]; then
ln -sf /var/lib/asterisk/sounds/custom /usr/share/asterisk/sounds/custom 2>/dev/null || true
fi
chown -R asterisk:asterisk /var/spool/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh 2>/dev/null || true
chmod 2775 /var/spool/asterisk /var/spool/asterisk/voicemail /var/lib/asterisk/sounds /var/lib/asterisk/moh 2>/dev/null || true
setfacl -R -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/spool/asterisk 2>/dev/null || true
+7 -11
View File
@@ -571,10 +571,6 @@ class ConfigGenerator
$lines[] = " same => n,Hangup()";
}
if ($groupCode !== '') {
$lines[] = "exten => _{$groupCode}.,1,NoOp(Interception d'appel dirigee vers \${EXTEN:" . strlen($groupCode) . "})";
$lines[] = " same => n,Pickup(\${EXTEN:" . strlen($groupCode) . "}@from-internal)";
$lines[] = " same => n,Hangup()";
$lines[] = "exten => {$groupCode},1,NoOp(Interception d'appel de groupe)";
$lines[] = " same => n,Pickup()";
$lines[] = " same => n,Hangup()";
@@ -986,9 +982,10 @@ class ConfigGenerator
$lines[] = "";
}
// Interphonie directe 1-à-1 (*80 + numéro de poste)
// Interphonie directe 1-à-1 (*80 + numéro de poste, ex: *80101 pour le poste 101)
// Pattern _*80XX. pour ne cibler que les numéros de postes (au moins 2 chiffres) et ne pas chevaucher *801
$lines[] = "; --- Interphonie directe 1-à-1 (*80 + numéro de poste) ---";
$lines[] = "exten => _*80.,1,NoOp(Interphonie directe vers poste \${EXTEN:3})";
$lines[] = "exten => _*80XX.,1,NoOp(Interphonie directe vers poste \${EXTEN:3})";
$lines[] = " same => n,Set(CHANNEL(language)={$sysLang})";
$lines[] = " same => n,Answer()";
$lines[] = " same => n,Playback(beep)";
@@ -997,13 +994,14 @@ class ConfigGenerator
$lines[] = "";
}
// Accès direct aux IVR depuis l'interne via *8 + id
// Accès direct aux IVR depuis l'interne via *8 + id (*801, *802...)
if (!empty($ivrs)) {
$lines[] = "; --- Test direct des SVI/IVR depuis l'interne ---";
$lines[] = "; --- Test direct des SVI/IVR depuis l'interne (*801, *802...) ---";
foreach ($ivrs as $ivr) {
$ivrSlug = $this->slugify($ivr['name']);
$testCode = '*8' . str_pad((string)$ivr['id'], 2, '0', STR_PAD_LEFT);
$lines[] = "exten => {$testCode},1,NoOp(Test SVI {$ivr['name']})";
$lines[] = " same => n,Set(CHANNEL(language)={$sysLang})";
$lines[] = " same => n,Goto(ivr-{$ivrSlug},s,1)";
}
$lines[] = "";
@@ -1500,6 +1498,7 @@ class ConfigGenerator
$lines[] = "; --- Serveur Vocal Interactif : {$ivr['name']} ---";
$lines[] = "[ivr-{$ivrSlug}]";
$lines[] = "exten => s,1,NoOp(SVI {$ivr['name']})";
$lines[] = " same => n,Set(CHANNEL(language)={$sysLang})";
$lines[] = " same => n,Answer()";
$lines[] = " same => n,Wait(1)";
$lines[] = " same => n,Set(IVR_INVALID_COUNT=0)";
@@ -1600,9 +1599,6 @@ class ConfigGenerator
return implode("\n", $lines) . "\n";
}
/**
* Sauvegarde les anciens fichiers et écrit atomiquement pjsip.custom.conf et extensions.custom.conf
* avec les droits Debian asterisk:asterisk (0660).
/**
* Génère les classes Musique d'attente (MOH) personnalisées par site/catégorie
*/
+39
View File
@@ -225,6 +225,45 @@ class SystemChecker
@mkdir('/var/spool/asterisk/voicemail/default', 0775, true);
// Liens vers les répertoires de sons Debian (/usr/share/asterisk/sounds -> /var/lib/asterisk/sounds)
@mkdir('/var/lib/asterisk/sounds', 0775, true);
@mkdir('/var/lib/asterisk/sounds/custom', 0775, true);
@mkdir('/var/lib/asterisk/moh', 0775, true);
foreach (['fr', 'en', 'es'] as $lang) {
$src = "/usr/share/asterisk/sounds/{$lang}";
$dst = "/var/lib/asterisk/sounds/{$lang}";
if (is_dir($src) && !file_exists($dst)) {
@symlink($src, $dst);
}
if (is_dir($dst) && !file_exists("{$dst}/custom")) {
@symlink('/var/lib/asterisk/sounds/custom', "{$dst}/custom");
}
}
if (!file_exists('/usr/share/asterisk/sounds/custom')) {
@symlink('/var/lib/asterisk/sounds/custom', '/usr/share/asterisk/sounds/custom');
}
// Synchroniser manager.conf avec le mot de passe AMI de la base SQLite si nécessaire (ex: après restauration de backup)
$mgrConf = '/etc/asterisk/manager.conf';
if (is_file($mgrConf) && is_writable($mgrConf)) {
try {
$db = Database::getConnection();
$st = $db->query('SELECT ami_user, ami_secret FROM settings WHERE id = 1')->fetch();
if (!empty($st['ami_secret'])) {
$sec = trim((string)$st['ami_secret']);
$usr = trim((string)($st['ami_user'] ?? 'pbxgui')) ?: 'pbxgui';
$content = (string)file_get_contents($mgrConf);
if (str_contains($content, "[{$usr}]")) {
$updated = preg_replace("/(\[{$usr}\][^\[]*secret\s*=\s*)([^\r\n]+)/i", "\${1}{$sec}", $content);
if ($updated !== null && $updated !== $content) {
file_put_contents($mgrConf, $updated);
@exec('asterisk -rx "manager reload" 2>/dev/null || true');
}
}
}
} catch (\Throwable) {}
}
$astConf = '/etc/asterisk/asterisk.conf';
if (is_file($astConf) && is_writable($astConf)) {
$content = (string)file_get_contents($astConf);
+36 -4
View File
@@ -298,17 +298,19 @@ class SystemMetrics
}
}
$seenExts = [];
foreach (preg_split('/\r?\n/', $rawContacts) ?: [] as $line) {
// Contact: 101/sip:101@10.10.167.162:5060 5dd9a21be8 Avail 9.234
// or with -nan: Contact: 102/sip:102@10.10.168.228:5060;x-ast-txp=trans bb0686ce80 Unavail -nan
if (preg_match('/Contact:\s+([^\s\/]+)\/sip:([^\s]+)\s+([a-f0-9]+)\s+(\S+)\s+(-?[0-9.]+|nan|-nan)/i', $line, $m)) {
// Supporte sip:, sips:, sans préfixe, tous types de hash et RTT (numérique, nan, N/A ou absent)
if (preg_match('/Contact:\s+([^\s\/]+)\/(?:sips?:)?([^\s]+)\s+([a-zA-Z0-9]+)\s+(\S+)(?:\s+(\S+))?/i', $line, $m)) {
$extNum = $m[1];
$uri = $m[2];
$status = $m[4];
$rttRaw = $m[5] ?? null;
$isAvail = (stripos($status, 'Avail') !== false && stripos($status, 'Unavail') === false) || stripos($status, 'NonQual') !== false;
if ($isAvail) {
$onlineCount++;
}
$seenExts[$extNum] = true;
$meta = $contactMeta[$extNum] ?? [];
$fullUri = $meta['uri'] ?? ("sip:" . $uri);
@@ -330,7 +332,37 @@ class SystemMetrics
'user_agent' => $userAgent,
'active_transport' => $activeTransport,
'status' => $status,
'rtt_ms' => is_numeric($m[5]) ? round((float)$m[5], 1) : null,
'rtt_ms' => ($rttRaw !== null && is_numeric($rttRaw)) ? round((float)$rttRaw, 1) : null,
];
}
}
// Filet de sécurité 100% infaillible : Tous les contacts enregistrés dans AstDB (/registrar/contact)
// non encore répertoriés (par exemple si qualify est désactivé ou RTT non disponible)
$nowTime = time();
foreach ($contactMeta as $extNum => $meta) {
if (isset($seenExts[$extNum])) {
continue;
}
$exp = (int)($meta['expiration_time'] ?? 0);
if ($exp === 0 || $exp > $nowTime) {
$onlineCount++;
$fullUri = $meta['uri'] ?? '';
$activeTransport = 'udp';
if (preg_match('/transport=(tls|wss|ws|tcp|udp)/i', $fullUri, $tm)) {
$activeTransport = strtolower($tm[1]);
} elseif (preg_match('/x-ast-txp=transport-(tls|wss|ws|tcp|udp)/i', $fullUri, $tm)) {
$activeTransport = strtolower($tm[1]);
} elseif (str_starts_with($fullUri, 'sips:')) {
$activeTransport = 'tls';
}
$contacts[] = [
'extension' => (string)$extNum,
'uri' => $fullUri,
'user_agent' => $meta['user_agent'] ?? '',
'active_transport' => $activeTransport,
'status' => 'Avail',
'rtt_ms' => null,
];
}
}
+5 -6
View File
@@ -1,13 +1,12 @@
{
"version": "1.4.6",
"version": "1.4.9",
"codename": "Helios",
"release_date": "2026-11-04",
"min_php_version": "8.2",
"changelog": [
"Fiabilisation clé en main de la messagerie vocale (*97 / *98) sur toute installation fraîche Debian (install.sh & fix.sh)",
"Neutralisation préventive des modules voicemail ODBC/IMAP incompatibles sur Debian (évite les conflits bloquant app_voicemail)",
"Inclusion automatique de voicemail.custom.conf et musiconhold.custom.conf dès l'installation",
"Génération et permissions strictes sur les répertoires de spool voicemail (/var/spool/asterisk/voicemail)",
"Contrôle d'intégrité renforcé dans le diagnostic système (SystemChecker)"
"Résolution du chemin audio des annonces personnalisées SVI sous Debian (/sounds/fr/custom/)",
"Restauration instantanée du statut en ligne des postes après restauration de sauvegarde (sans délai de Qualify)",
"Correction des conflits de préfixes Dialplan (*801-*899 vs *8 / *80)",
"Code propre, robuste et optimisé sans surcharge"
]
}