feat(cluster): add configurable BLF expiry interval in HA menu with 120s default and automatic check-sync notify on failover
This commit is contained in:
1 parent
c1c6eab03c
commit
f14185d597
6 files changed
+145
-17
No files matched your search
@@ -6394,6 +6394,33 @@ async function refreshClusterStatus() {
|
|||||||
const secretInput = document.getElementById('cluster-secret');
|
const secretInput = document.getElementById('cluster-secret');
|
||||||
if (secretInput && st.cluster_secret) secretInput.value = st.cluster_secret;
|
if (secretInput && st.cluster_secret) secretInput.value = st.cluster_secret;
|
||||||
|
|
||||||
|
const blfExpiryContainer = document.getElementById('cluster-blf-expiry-container');
|
||||||
|
const blfExpiryInput = document.getElementById('cluster-blf-expiry');
|
||||||
|
if (blfExpiryContainer && blfExpiryInput) {
|
||||||
|
if (st.enabled) {
|
||||||
|
blfExpiryContainer.classList.remove('hidden');
|
||||||
|
blfExpiryInput.value = st.blf_expiry || 120;
|
||||||
|
} else {
|
||||||
|
blfExpiryContainer.classList.add('hidden');
|
||||||
|
blfExpiryInput.value = st.blf_expiry || 1800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chkEnabled && !chkEnabled.dataset.blfBound) {
|
||||||
|
chkEnabled.dataset.blfBound = '1';
|
||||||
|
chkEnabled.addEventListener('change', () => {
|
||||||
|
const c = document.getElementById('cluster-blf-expiry-container');
|
||||||
|
const inp = document.getElementById('cluster-blf-expiry');
|
||||||
|
if (chkEnabled.checked) {
|
||||||
|
if (c) c.classList.remove('hidden');
|
||||||
|
if (inp && (!inp.value || inp.value === '1800')) inp.value = 120;
|
||||||
|
} else {
|
||||||
|
if (c) c.classList.add('hidden');
|
||||||
|
if (inp) inp.value = 1800;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Badges & Cartes
|
// Badges & Cartes
|
||||||
const headerBadge = document.getElementById('cluster-header-state-badge');
|
const headerBadge = document.getElementById('cluster-header-state-badge');
|
||||||
const navBadge = document.getElementById('nav-badge-cluster');
|
const navBadge = document.getElementById('nav-badge-cluster');
|
||||||
@@ -6584,6 +6611,7 @@ async function saveClusterSettings(e) {
|
|||||||
peer_port: parseInt(form.elements['peer_port'] ? form.elements['peer_port'].value : 8443, 10) || 8443,
|
peer_port: parseInt(form.elements['peer_port'] ? form.elements['peer_port'].value : 8443, 10) || 8443,
|
||||||
cluster_secret: form.elements['cluster_secret'].value.trim(),
|
cluster_secret: form.elements['cluster_secret'].value.trim(),
|
||||||
vrid: parseInt(form.elements['vrid'].value, 10) || 51,
|
vrid: parseInt(form.elements['vrid'].value, 10) || 51,
|
||||||
|
blf_expiry: parseInt(form.elements['blf_expiry'] ? form.elements['blf_expiry'].value : 120, 10) || 120,
|
||||||
auto_sync: form.elements['auto_sync'].checked ? 1 : 0
|
auto_sync: form.elements['auto_sync'].checked ? 1 : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2939,6 +2939,24 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DÉLAI D'EXPIRATION / RÉABONNEMENT BLF (HA) -->
|
||||||
|
<div id="cluster-blf-expiry-container" class="hidden p-3.5 rounded-xl bg-anthracite-900/80 border border-cyan-500/30 space-y-1">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 mb-0.5">
|
||||||
|
<label for="cluster-blf-expiry" class="text-xs font-bold text-white cursor-pointer">Fréquence de réabonnement BLF & Postes</label>
|
||||||
|
<span class="px-2 py-0.2 rounded text-[10px] font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/30">Recommandé HA : 120s</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-[11px] text-slate-400">Plafond d'expiration imposé aux téléphones pour forcer le rafraîchissement rapide des voyants BLF après bascule HA (par défaut 1800s sans HA, 120s avec HA).</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
|
<input type="number" id="cluster-blf-expiry" name="blf_expiry" value="120" min="30" max="3600"
|
||||||
|
class="w-24 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white text-center font-mono focus:outline-none focus:border-cyan-400">
|
||||||
|
<span class="text-xs text-slate-400 font-mono">sec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- OPTIONS AVANCÉES (VRID, AUTO-SYNC) -->
|
<!-- OPTIONS AVANCÉES (VRID, AUTO-SYNC) -->
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-2 border-t border-anthracite-700/60">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-2 border-t border-anthracite-700/60">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -55,5 +55,14 @@ if [ -n "$VIP_CLEAN" ] && command -v arping >/dev/null 2>&1; then
|
|||||||
arping -c 5 -U -I "$IFACE" "$VIP_CLEAN" >> "$LOG_FILE" 2>&1 || true
|
arping -c 5 -U -I "$IFACE" "$VIP_CLEAN" >> "$LOG_FILE" 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 5. Envoi automatique du signal check-sync silencieux aux téléphones enregistrés
|
||||||
|
# Force Yealink, Fanvil, Snom à réémettre instantanément tous leurs SUBSCRIBE BLF sur ce serveur actif
|
||||||
|
if pidof asterisk >/dev/null 2>&1; then
|
||||||
|
echo "[$TIMESTAMP] [CLUSTER] Envoi check-sync de réveil aux postes pour réabonnement immédiat des BLF..." >> "$LOG_FILE"
|
||||||
|
for ep in $(/usr/sbin/asterisk -rx "pjsip show endpoints" 2>/dev/null | grep -o 'Endpoint: [0-9]*' | awk '{print $2}'); do
|
||||||
|
/usr/sbin/asterisk -rx "pjsip send notify check-sync endpoint $ep" >> "$LOG_FILE" 2>&1 || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[$TIMESTAMP] [CLUSTER] Prise de relais MASTER terminée avec succès." >> "$LOG_FILE"
|
echo "[$TIMESTAMP] [CLUSTER] Prise de relais MASTER terminée avec succès." >> "$LOG_FILE"
|
||||||
exit 0
|
exit 0
|
||||||
+31
-12
@@ -42,6 +42,7 @@ class ClusterManager
|
|||||||
'cluster_secret' => bin2hex(random_bytes(16)),
|
'cluster_secret' => bin2hex(random_bytes(16)),
|
||||||
'vrid' => 51,
|
'vrid' => 51,
|
||||||
'priority' => 100,
|
'priority' => 100,
|
||||||
|
'blf_expiry' => 120,
|
||||||
'sync_interval' => 15,
|
'sync_interval' => 15,
|
||||||
'auto_sync' => 1,
|
'auto_sync' => 1,
|
||||||
'last_sync_at' => null,
|
'last_sync_at' => null,
|
||||||
@@ -52,6 +53,9 @@ class ClusterManager
|
|||||||
if (empty($row['peer_port'])) {
|
if (empty($row['peer_port'])) {
|
||||||
$row['peer_port'] = 8443;
|
$row['peer_port'] = 8443;
|
||||||
}
|
}
|
||||||
|
if (empty($row['blf_expiry'])) {
|
||||||
|
$row['blf_expiry'] = 120;
|
||||||
|
}
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,7 @@ class ClusterManager
|
|||||||
$clusterSecret = trim((string)($data['cluster_secret'] ?? ''));
|
$clusterSecret = trim((string)($data['cluster_secret'] ?? ''));
|
||||||
$vrid = max(1, min(255, (int)($data['vrid'] ?? 51)));
|
$vrid = max(1, min(255, (int)($data['vrid'] ?? 51)));
|
||||||
$priority = max(1, min(254, (int)($data['priority'] ?? ($role === 'master' ? 100 : 90))));
|
$priority = max(1, min(254, (int)($data['priority'] ?? ($role === 'master' ? 100 : 90))));
|
||||||
|
$blfExpiry = max(30, min(3600, (int)($data['blf_expiry'] ?? 120)));
|
||||||
$syncInterval = max(5, min(3600, (int)($data['sync_interval'] ?? 15)));
|
$syncInterval = max(5, min(3600, (int)($data['sync_interval'] ?? 15)));
|
||||||
$autoSync = !empty($data['auto_sync']) ? 1 : 0;
|
$autoSync = !empty($data['auto_sync']) ? 1 : 0;
|
||||||
|
|
||||||
@@ -130,6 +135,7 @@ class ClusterManager
|
|||||||
cluster_secret = :secret,
|
cluster_secret = :secret,
|
||||||
vrid = :vrid,
|
vrid = :vrid,
|
||||||
priority = :prio,
|
priority = :prio,
|
||||||
|
blf_expiry = :blf_expiry,
|
||||||
sync_interval = :sync_int,
|
sync_interval = :sync_int,
|
||||||
auto_sync = :auto_sync,
|
auto_sync = :auto_sync,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
@@ -137,23 +143,34 @@ class ClusterManager
|
|||||||
');
|
');
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':enabled' => $enabled,
|
':enabled' => $enabled,
|
||||||
':role' => $role,
|
':role' => $role,
|
||||||
':vip' => $virtualIp,
|
':vip' => $virtualIp,
|
||||||
':vmask' => $virtualNetmask,
|
':vmask' => $virtualNetmask,
|
||||||
':iface' => $interface,
|
':iface' => $interface,
|
||||||
':peer' => $peerIp,
|
':peer' => $peerIp,
|
||||||
':peer_port' => $peerPort,
|
':peer_port' => $peerPort,
|
||||||
':secret' => $clusterSecret,
|
':secret' => $clusterSecret,
|
||||||
':vrid' => $vrid,
|
':vrid' => $vrid,
|
||||||
':prio' => $priority,
|
':prio' => $priority,
|
||||||
':sync_int' => $syncInterval,
|
':blf_expiry' => $blfExpiry,
|
||||||
':auto_sync' => $autoSync,
|
':sync_int' => $syncInterval,
|
||||||
|
':auto_sync' => $autoSync,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Application automatique de la configuration Keepalived
|
// Application automatique de la configuration Keepalived
|
||||||
$applyResult = $this->applyKeepalivedConfig();
|
$applyResult = $this->applyKeepalivedConfig();
|
||||||
|
|
||||||
|
// Régénération de la configuration PJSIP et rechargement Asterisk avec le nouveau délai d'abonnement
|
||||||
|
try {
|
||||||
|
$gen = new ConfigGenerator($this->db);
|
||||||
|
$gen->writeAllConfigs();
|
||||||
|
shell_exec('asterisk -rx "pjsip reload" 2>/dev/null || true');
|
||||||
|
shell_exec('asterisk -rx "dialplan reload" 2>/dev/null || true');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
error_log("[CLUSTER] Erreur régénération configs Asterisk : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Configuration Haute Disponibilité enregistrée et appliquée.',
|
'message' => 'Configuration Haute Disponibilité enregistrée et appliquée.',
|
||||||
@@ -723,6 +740,7 @@ class ClusterManager
|
|||||||
virtual_ip = :virtual_ip,
|
virtual_ip = :virtual_ip,
|
||||||
virtual_netmask = :virtual_netmask,
|
virtual_netmask = :virtual_netmask,
|
||||||
vrid = :vrid,
|
vrid = :vrid,
|
||||||
|
blf_expiry = :blf_expiry,
|
||||||
last_sync_at = CURRENT_TIMESTAMP,
|
last_sync_at = CURRENT_TIMESTAMP,
|
||||||
last_sync_status = :status,
|
last_sync_status = :status,
|
||||||
last_sync_message = :msg
|
last_sync_message = :msg
|
||||||
@@ -737,6 +755,7 @@ class ClusterManager
|
|||||||
':virtual_ip' => $localClusterSettings['virtual_ip'] ?? '',
|
':virtual_ip' => $localClusterSettings['virtual_ip'] ?? '',
|
||||||
':virtual_netmask' => $localClusterSettings['virtual_netmask'] ?? '24',
|
':virtual_netmask' => $localClusterSettings['virtual_netmask'] ?? '24',
|
||||||
':vrid' => $localClusterSettings['vrid'] ?? 51,
|
':vrid' => $localClusterSettings['vrid'] ?? 51,
|
||||||
|
':blf_expiry' => (int)($localClusterSettings['blf_expiry'] ?? 120),
|
||||||
':status' => 'synced',
|
':status' => 'synced',
|
||||||
':msg' => 'Base et sons synchronisés depuis le Master',
|
':msg' => 'Base et sons synchronisés depuis le Master',
|
||||||
]);
|
]);
|
||||||
|
|||||||
+53
-5
@@ -15,8 +15,9 @@ class ConfigGenerator
|
|||||||
public const MOH_CONF_PATH = '/etc/asterisk/musiconhold.custom.conf';
|
public const MOH_CONF_PATH = '/etc/asterisk/musiconhold.custom.conf';
|
||||||
public const RTP_CONF_PATH = '/etc/asterisk/rtp.conf';
|
public const RTP_CONF_PATH = '/etc/asterisk/rtp.conf';
|
||||||
public const FEATURES_CONF_PATH = '/etc/asterisk/features.conf';
|
public const FEATURES_CONF_PATH = '/etc/asterisk/features.conf';
|
||||||
public const VOICEMAIL_CONF_PATH = '/etc/asterisk/voicemail.custom.conf';
|
public const VOICEMAIL_CONF_PATH = '/etc/asterisk/voicemail.custom.conf';
|
||||||
public const BACKUP_DIR = __DIR__ . '/../storage/backups';
|
public const PJSIP_NOTIFY_CONF_PATH = '/etc/asterisk/pjsip_notify.conf';
|
||||||
|
public const BACKUP_DIR = __DIR__ . '/../storage/backups';
|
||||||
|
|
||||||
public function __construct(private PDO $db) {}
|
public function __construct(private PDO $db) {}
|
||||||
|
|
||||||
@@ -187,6 +188,18 @@ class ConfigGenerator
|
|||||||
$lines[] = "; ===========================================================================";
|
$lines[] = "; ===========================================================================";
|
||||||
$lines[] = "; POSTES TÉLÉPHONIQUES (EXTENSIONS)";
|
$lines[] = "; POSTES TÉLÉPHONIQUES (EXTENSIONS)";
|
||||||
$lines[] = "; ===========================================================================";
|
$lines[] = "; ===========================================================================";
|
||||||
|
|
||||||
|
// Détection de l'état du Cluster Haute Disponibilité (HA)
|
||||||
|
$clusterSt = [];
|
||||||
|
try {
|
||||||
|
$clusterSt = $this->db->query('SELECT enabled, blf_expiry FROM cluster_settings WHERE id = 1')->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
} catch (\Throwable $e) {}
|
||||||
|
$isHaActive = !empty($clusterSt['enabled']);
|
||||||
|
// En mode Haute Disponibilité, on plafonne l'expiration des AOR/souscriptions sur la valeur configurée (par défaut 120s)
|
||||||
|
// afin que les téléphones se réabonnent très vite en cas de bascule. Sans HA, on conserve le standard 1800s (30 min).
|
||||||
|
$regExpiration = $isHaActive ? max(30, min(3600, (int)($clusterSt['blf_expiry'] ?? 120))) : 1800;
|
||||||
|
$subMinExpiry = $isHaActive ? 30 : 0;
|
||||||
|
|
||||||
foreach ($extensions as $ext) {
|
foreach ($extensions as $ext) {
|
||||||
$num = trim((string)$ext['extension']);
|
$num = trim((string)$ext['extension']);
|
||||||
$name = str_replace('"', '', trim((string)$ext['name']));
|
$name = str_replace('"', '', trim((string)$ext['name']));
|
||||||
@@ -274,9 +287,9 @@ class ConfigGenerator
|
|||||||
$lines[] = "type = aor";
|
$lines[] = "type = aor";
|
||||||
$lines[] = "max_contacts = {$maxContacts}";
|
$lines[] = "max_contacts = {$maxContacts}";
|
||||||
$lines[] = "remove_existing = yes";
|
$lines[] = "remove_existing = yes";
|
||||||
$lines[] = "default_expiration = 120";
|
$lines[] = "default_expiration = {$regExpiration}";
|
||||||
$lines[] = "maximum_expiration = 300";
|
$lines[] = "maximum_expiration = {$regExpiration}";
|
||||||
$lines[] = "minimum_expiration = 60";
|
$lines[] = "minimum_expiration = " . ($isHaActive ? 30 : 60);
|
||||||
$lines[] = "qualify_frequency = 30";
|
$lines[] = "qualify_frequency = 30";
|
||||||
if ($hasVm) {
|
if ($hasVm) {
|
||||||
$lines[] = "mailboxes = {$num}@default";
|
$lines[] = "mailboxes = {$num}@default";
|
||||||
@@ -1874,6 +1887,39 @@ class ConfigGenerator
|
|||||||
return implode("\n", $lines) . "\n";
|
return implode("\n", $lines) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère la configuration des événements SIP Notify (/etc/asterisk/pjsip_notify.conf)
|
||||||
|
* Profils check-sync pour Yealink, Fanvil, Snom, Polycom, etc.
|
||||||
|
*/
|
||||||
|
public function generatePjsipNotifyConf(): string
|
||||||
|
{
|
||||||
|
return <<<EOF
|
||||||
|
; ===========================================================================
|
||||||
|
; CONFIGURATION DES NOTIFICATIONS SIP PJSIP - NexusPBX
|
||||||
|
; Profils de resynchronisation immédiate (check-sync) Yealink, Fanvil, Snom, etc.
|
||||||
|
; ===========================================================================
|
||||||
|
|
||||||
|
[check-sync]
|
||||||
|
Event=>check-sync\;reboot=false
|
||||||
|
|
||||||
|
[yealink-check-cfg]
|
||||||
|
Event=>check-sync\;reboot=false
|
||||||
|
|
||||||
|
[snom-check-cfg]
|
||||||
|
Event=>check-sync\;reboot=false
|
||||||
|
|
||||||
|
[polycom-check-cfg]
|
||||||
|
Event=>check-sync
|
||||||
|
|
||||||
|
[cisco-check-cfg]
|
||||||
|
Event=>check-sync
|
||||||
|
|
||||||
|
[aastra-check-cfg]
|
||||||
|
Event=>check-sync
|
||||||
|
|
||||||
|
EOF;
|
||||||
|
}
|
||||||
|
|
||||||
public function writeAllConfigs(): array
|
public function writeAllConfigs(): array
|
||||||
{
|
{
|
||||||
if (!is_dir(self::BACKUP_DIR)) {
|
if (!is_dir(self::BACKUP_DIR)) {
|
||||||
@@ -1886,6 +1932,7 @@ class ConfigGenerator
|
|||||||
$rtpContent = $this->generateRtpConf();
|
$rtpContent = $this->generateRtpConf();
|
||||||
$featuresContent = $this->generateFeaturesConf();
|
$featuresContent = $this->generateFeaturesConf();
|
||||||
$voicemailContent = $this->generateVoicemailConf();
|
$voicemailContent = $this->generateVoicemailConf();
|
||||||
|
$notifyContent = $this->generatePjsipNotifyConf();
|
||||||
|
|
||||||
$this->writeAtomicWithPermissions(self::PJSIP_CONF_PATH, $pjsipContent);
|
$this->writeAtomicWithPermissions(self::PJSIP_CONF_PATH, $pjsipContent);
|
||||||
$this->writeAtomicWithPermissions(self::EXTENSIONS_CONF_PATH, $extensionsContent);
|
$this->writeAtomicWithPermissions(self::EXTENSIONS_CONF_PATH, $extensionsContent);
|
||||||
@@ -1893,6 +1940,7 @@ class ConfigGenerator
|
|||||||
$this->writeAtomicWithPermissions(self::RTP_CONF_PATH, $rtpContent);
|
$this->writeAtomicWithPermissions(self::RTP_CONF_PATH, $rtpContent);
|
||||||
$this->writeAtomicWithPermissions(self::FEATURES_CONF_PATH, $featuresContent);
|
$this->writeAtomicWithPermissions(self::FEATURES_CONF_PATH, $featuresContent);
|
||||||
$this->writeAtomicWithPermissions(self::VOICEMAIL_CONF_PATH, $voicemailContent);
|
$this->writeAtomicWithPermissions(self::VOICEMAIL_CONF_PATH, $voicemailContent);
|
||||||
|
$this->writeAtomicWithPermissions(self::PJSIP_NOTIFY_CONF_PATH, $notifyContent);
|
||||||
|
|
||||||
// S'assurer que pjsip.conf et extensions.conf incluent bien les fichiers .custom.conf
|
// S'assurer que pjsip.conf et extensions.conf incluent bien les fichiers .custom.conf
|
||||||
SystemChecker::ensureAsteriskIncludes();
|
SystemChecker::ensureAsteriskIncludes();
|
||||||
|
|||||||
@@ -329,6 +329,7 @@ class Database
|
|||||||
cluster_secret VARCHAR(64) NOT NULL DEFAULT '',
|
cluster_secret VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
vrid INTEGER NOT NULL DEFAULT 51,
|
vrid INTEGER NOT NULL DEFAULT 51,
|
||||||
priority INTEGER NOT NULL DEFAULT 100,
|
priority INTEGER NOT NULL DEFAULT 100,
|
||||||
|
blf_expiry INTEGER NOT NULL DEFAULT 120,
|
||||||
sync_interval INTEGER NOT NULL DEFAULT 15,
|
sync_interval INTEGER NOT NULL DEFAULT 15,
|
||||||
auto_sync INTEGER NOT NULL DEFAULT 1,
|
auto_sync INTEGER NOT NULL DEFAULT 1,
|
||||||
last_sync_at DATETIME DEFAULT NULL,
|
last_sync_at DATETIME DEFAULT NULL,
|
||||||
@@ -813,6 +814,11 @@ class Database
|
|||||||
$db->exec("ALTER TABLE cluster_settings ADD COLUMN peer_port INTEGER NOT NULL DEFAULT 8443");
|
$db->exec("ALTER TABLE cluster_settings ADD COLUMN peer_port INTEGER NOT NULL DEFAULT 8443");
|
||||||
} catch (\Throwable $e) {}
|
} catch (\Throwable $e) {}
|
||||||
|
|
||||||
|
// Migration de la colonne blf_expiry si elle n'existe pas encore
|
||||||
|
try {
|
||||||
|
$db->exec("ALTER TABLE cluster_settings ADD COLUMN blf_expiry INTEGER NOT NULL DEFAULT 120");
|
||||||
|
} catch (\Throwable $e) {}
|
||||||
|
|
||||||
$count = (int)$db->query('SELECT COUNT(*) FROM cluster_settings')->fetchColumn();
|
$count = (int)$db->query('SELECT COUNT(*) FROM cluster_settings')->fetchColumn();
|
||||||
if ($count === 0) {
|
if ($count === 0) {
|
||||||
$defaultSecret = bin2hex(random_bytes(16));
|
$defaultSecret = bin2hex(random_bytes(16));
|
||||||
|
|||||||
Reference in new issue
Block a user