Show dung lượng hiện tại: df -h
Check dung lượng các file log: du -sh /var/lib/my*ql/*
Vào MySQL check tiếp: mysql -uroot -p
Dọn log, chỉnh theo số ngày mong muốn, sau đó check lại với df -h: PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY;
Show dung lượng hiện tại: df -h
Check dung lượng các file log: du -sh /var/lib/my*ql/*
Vào MySQL check tiếp: mysql -uroot -p
Dọn log, chỉnh theo số ngày mong muốn, sau đó check lại với df -h: PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY;
<?php
function h($s) {
return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
}
function cleanParams(array $input): array {
$clean = [];
foreach ($input as $key => $value) {
$key = preg_replace('/^amp;/', '', (string)$key);
$clean[$key] = $value;
}
return $clean;
}
$params = cleanParams($_POST);
// =======================
// CONFIG
// =======================
$defaultNbiHost = 'thay IP controller vào đây';
$nbiUser = 'nbi-acc';
$nbiPass = 'pass của nbi-acc';
// AAA Always Accept nên user/pass này chỉ là dummy
$ueUsername = 'ok';
$uePassword = 'ok';
// Chỉ cho phép gọi đúng vSZ NBI này
$allowedNbiHosts = [
'thay IP controller vào đây',
];
// =======================
// INPUT FROM vSZ
// =======================
$nbiHost = $params['nbiIP'] ?? $defaultNbiHost;
if (!in_array($nbiHost, $allowedNbiHosts, true)) {
$nbiHost = $defaultNbiHost;
}
$nbiUrl = 'https://' . $nbiHost . ':9443/portalintf';
$ueIp = $params['uip'] ?? '';
$ueMac = $params['client_mac'] ?? '';
$ueProxy = $params['proxy'] ?? '0';
if ($ueIp === '' || $ueMac === '') {
http_response_code(400);
echo 'Missing uip or client_mac';
exit;
}
// =======================
// BUILD PAYLOAD
// =======================
$payload = [
'Vendor' => 'ruckus',
'APIVersion' => '1.0',
'RequestCategory' => 'UserOnlineControl',
'RequestType' => 'Login',
'RequestUserName' => $nbiUser,
'RequestPassword' => $nbiPass,
'UE-IP' => $ueIp,
'UE-MAC' => $ueMac,
'UE-Proxy' => $ueProxy,
'UE-Username' => $ueUsername,
'UE-Password' => $uePassword,
];
// =======================
// CALL vSZ NBI
// =======================
$ch = curl_init($nbiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
// vSZ cert đang expired/self-signed thì để false.
// Khi thay cert chuẩn có thể bật verify lại.
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$curlNo = curl_errno($ch);
$curlErr = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$info = curl_getinfo($ch);
curl_close($ch);
// =======================
// LOG DEBUG
// =======================
$logPayload = $payload;
$logPayload['RequestPassword'] = '***';
/*
file_put_contents(
__DIR__ . '/nbi-login.log',
date('c') .
' URL=' . $nbiUrl .
' HTTP=' . $httpCode .
' CURL_NO=' . $curlNo .
' CURL_ERR=' . $curlErr .
' INFO=' . json_encode($info, JSON_UNESCAPED_SLASHES) .
' PAYLOAD=' . json_encode($logPayload, JSON_UNESCAPED_SLASHES) .
' RESPONSE=' . $response .
PHP_EOL,
FILE_APPEND
);
*/
// =======================
// CHECK CURL RESULT
// =======================
if ($response === false || $curlNo !== 0 || $curlErr !== '' || $httpCode < 200 || $httpCode >= 300) {
http_response_code(500);
echo '<pre>';
echo "NBI request failed\n";
echo "HTTP: " . h($httpCode) . "\n";
echo "CURL_NO: " . h($curlNo) . "\n";
echo "CURL_ERR: " . h($curlErr) . "\n";
echo "Response:\n" . h($response) . "\n";
echo '</pre>';
exit;
}
$data = json_decode($response, true);
$responseCode = isset($data['ResponseCode']) ? (int)$data['ResponseCode'] : 0;
$replyMessage = $data['ReplyMessage'] ?? '';
// vSZ có thể trả:
// 201 = Login succeeded
// 101 = Client authorized
$successCodes = [101, 201];
if (!in_array($responseCode, $successCodes, true)) {
http_response_code(500);
echo '<pre>';
echo "NBI login not successful\n";
echo "ResponseCode: " . h($responseCode) . "\n";
echo "ReplyMessage: " . h($replyMessage) . "\n";
echo "Full response:\n" . h($response) . "\n";
echo '</pre>';
exit;
}
// =======================
// SUCCESS PAGE
// =======================
header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
?>
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>Connected</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: Arial, sans-serif;
background: #111827;
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.box {
width: 90%;
max-width: 360px;
background: #ffffff;
color: #111827;
padding: 28px;
border-radius: 16px;
box-shadow: 0 20px 50px rgba(0,0,0,.35);
}
.check {
width: 64px;
height: 64px;
margin: 0 auto 16px;
border-radius: 50%;
background: #16a34a;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
font-weight: bold;
}
h2 { margin: 0 0 10px; font-size: 24px; }
p { margin: 0; color: #4b5563; line-height: 1.5; }
.small { margin-top: 12px; font-size: 12px; color: #6b7280; }
.dots span {
animation: bounce 1.2s infinite ease-in-out;
display: inline-block;
}
.dots span:nth-child(2) { animation-delay: 0.2s; }
.dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
40% { transform: translateY(-5px); opacity: 1; }
}
#preload-frame {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
border: none;
}
</style>
</head>
<body>
<div class="box">
<div class="check">✓</div>
<h2>Connected</h2>
<p>Bạn đã kết nối Internet.</p>
<div class="small dots">Đang chuyển hướng<span>.</span><span>.</span><span>.</span></div>
</div>
<iframe id="preload-frame"></iframe>
<script>
const TARGET = 'https://tamanhhospital.vn/vu-tru-robot/?utm_campaign=bvta_longbien_wifi-sitelongbien_vutrurobot2026_he-thong-may-moc_traffic&utm_source=wifi-sitelongbien&utm_medium=wifi-marketing&utm_content=robot-traffic&utm_term=mass';
const MIN_DISPLAY_MS = 4000; // minimum time to show this screen
const start = Date.now();
let iframeReady = false;
let timerDone = false;
function tryRedirect() {
if (iframeReady || timerDone) {
window.location.replace(TARGET);
}
}
// Start preloading in hidden iframe
const frame = document.getElementById('preload-frame');
frame.src = TARGET;
frame.onload = function () {
iframeReady = true;
tryRedirect();
};
frame.onerror = function () {
// If iframe fails (e.g. X-Frame-Options blocks it), redirect anyway
iframeReady = true;
tryRedirect();
};
// Minimum display timer
setTimeout(function () {
timerDone = true;
tryRedirect();
}, MIN_DISPLAY_MS);
</script>
</body>
</html>
<?php
exit;
Tạo VBscript:
Set WshShell = CreateObject("WScript.Shell")
cmd = "robocopy ""Source-path"" ""Target=path"" /E /XO /Z /FFT /R:2 /W:5 /MT:16"
WshShell.Run cmd, 0, True
Tạo task schdule chạy mỗi 60 phút:
schtasks /Create /TN "ZaloData Robocopy" /SC MINUTE /MO 60 /TR "wscript.exe \"Script-path\DataBackup.vbs\"" /F
Thực hiện tạo 1 folder trống rồi robocopy kiểu mirror tới folder bị lỗi:
robocopy C:\Empty "D:\Backup path" /MIR
Sau đó sẽ xóa được
# trước khi chạy: kiểm tra csv dạng UTF-8, check duplicate MAC
# ghi đè lên các bản ghi đã có nếu trùng MAC
# Xóa các reserve có trên DHCP nhưng không có trong file. Không xóa các bản ghi DHCP bình thường
# Ghi log ra file CSV kèm thời gian, action
# ================= CONFIG =================
$ScopeId = "x.x.x.x"
$CsvPath = "C:\bat\Reserve-MAC-IP.csv"
$LogPath = "C:\bat\Dhcp-Reserve-Log.csv"
$rawCsv = Import-Csv -Path $CsvPath -Encoding UTF8
# ================= FUNCTION =================
# Format MAC về aa-bb-cc-dd-ee-ff
function Format-Mac($mac) {
$mac = $mac -replace "[:-]", ""
return ($mac.ToLower() -split '(.{2})' | Where-Object { $_ }) -join "-"
}
function Write-Log($mac, $ip, $desc, $action) {
$time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logObj = [PSCustomObject]@{
"MAC" = $mac
"IP" = $ip
"Description" = $desc
"Action" = $action
"Time" = $time
}
Write-Host ("[{0}] {1} | {2} | {3} | {4}" -f $time, $action, $mac, $ip, $desc)
if (!(Test-Path $LogPath)) {
$logObj | Export-Csv -Path $LogPath -NoTypeInformation -Encoding UTF8
} else {
$logObj | Export-Csv -Path $LogPath -NoTypeInformation -Append -Encoding UTF8
}
}
# ================= NORMALIZE CSV =================
$csvData = $rawCsv | ForEach-Object {
[PSCustomObject]@{
MAC = (Format-Mac $_.'MAC validate')
IP = $_.'IP Reserve'
Description = $_.Description
}
}
# ================= CHECK DUPLICATE MAC =================
$dupMac = $csvData | Group-Object MAC | Where-Object { $_.Count -gt 1 }
foreach ($d in $dupMac) {
Write-Host ("WARNING: DUPLICATE MAC {0} ({1} records) -> giữ dòng cuối" -f $d.Name, $d.Count) -ForegroundColor Yellow
}
# ================= REMOVE DUPLICATE (GIỮ DÒNG CUỐI) =================
$csvData = $csvData |
Group-Object MAC |
ForEach-Object { $_.Group[-1] }
# ================= GET EXISTING =================
$existingReservations = Get-DhcpServerv4Reservation -ScopeId $ScopeId
$existingByMac = @{}
foreach ($res in $existingReservations) {
$existingByMac[$res.ClientId.ToLower()] = $res
}
$csvMacSet = @{}
# ================= ADD / OVERWRITE =================
foreach ($row in $csvData) {
$mac = $row.MAC
$ip = $row.IP
$desc = $row.Description
$csvMacSet[$mac.ToLower()] = $true
if ($existingByMac.ContainsKey($mac.ToLower())) {
try {
Remove-DhcpServerv4Reservation -ScopeId $ScopeId -ClientId $mac -Confirm:$false -ErrorAction Stop
Add-DhcpServerv4Reservation -ScopeId $ScopeId -IPAddress $ip -ClientId $mac -Description $desc -ErrorAction Stop
Write-Log $mac $ip $desc "Overwrite"
} catch {
Write-Host "ERROR Overwrite: $mac" -ForegroundColor Red
}
} else {
try {
Add-DhcpServerv4Reservation -ScopeId $ScopeId -IPAddress $ip -ClientId $mac -Description $desc -ErrorAction Stop
Write-Log $mac $ip $desc "Add"
} catch {
Write-Host "ERROR Add: $mac" -ForegroundColor Red
}
}
}
# ================= DELETE =================
foreach ($res in $existingReservations) {
$mac = $res.ClientId.ToLower()
if (-not $csvMacSet.ContainsKey($mac)) {
try {
Remove-DhcpServerv4Reservation -ScopeId $ScopeId -ClientId $res.ClientId -Confirm:$false -ErrorAction Stop
Write-Log $res.ClientId $res.IPAddress $res.Description "Delete"
} catch {
Write-Host "ERROR Delete: $mac" -ForegroundColor Red
}
}
}
Sử dụng NAS Synology (xpen)
Sử dụng domain free qua ddns
Truy cập domain => lỗi cert
Phương án xử lý:
- Sử dụng ddns của Synology => cần có tài khoản Syno mới tạo đc domain và get cert, xpen thì ko login tài khoản Synology đc
- Sử dụng ddns bên ngoài như noip => domain riêng, quá trình get cert cần thông từ internet tới port 80 trên NAS:
Hiện chưa có cách gì. Hướng xử lý có thể nghĩ tới:
- Ôm cục NAS tới mạng khác cho phép mở port 80, chờ ddns update IP mới rồi thực hiện get cert
- Tìm cách login đc tài khoản Synology lên NAS => tạo domain theo Syno
Cấu hình SSID:
Bật DNS Google hoặc DNS public khác
Authentication type: Hotspot (WISPr)
Hotspot (WISPr) Portal: theo cấu hình portal đã tạo
Authentication server: bật Use Controller as proxy và chọn Always Accept
Tạo tài khoản Northbound Interface nbi-acc để portal xác thực với vSZ
Add IP của MKT-server vào Walled garden
Mở rule:
- tạm đi internet cho Rocky để cài package
- Guest tới MKT-server port http
- MKT-server tới vSZ port 9443
Dựng VM Rocky
Chạy playbook new_server, và:
dnf install -y nginx php php-fpm php-cli php-curl php-json policycoreutils-python-utils firewalld
systemctl enable --now nginx
systemctl enable --now php-fpm
systemctl enable --now firewalld
setsebool -P httpd_can_network_connect 1
firewall-cmd --permanent --add-service=http
firewall-cmd --reload
SSH vào thiết bị
Chạy Show configuration
Tìm dòng SKU để lấy serial:
SKU
---
Part Number: E7W00A
Serial Number: xxx
Revision: F
Show dung lượng hiện tại: df -h Check dung lượng các file log: du -sh /var/lib/my*ql/* Vào MySQL check tiếp: mysql -uroot -p Dọn log, chỉnh ...