From bea0c5c20008ce3b60b91cea8482ff9acc10bd64 Mon Sep 17 00:00:00 2001 From: deploy Date: Sat, 27 Jun 2026 21:44:44 +0000 Subject: [PATCH] commit-all-changes --- api/contadores_json.php | 70 +- api/listener_recovery.php | 549 ++++--- api/menu.php | 164 +- api/mqtt.php | 22 +- api/reset_parciais.php | 88 +- api/reset_pass.php | 22 +- api/send_cmd.php | 68 +- api/sniff_once.php | 46 +- api/validar.php | 2 +- .../php-mqtt/client/.github/release.yml | 50 +- comandosbck.php | 110 +- config.php | 22 +- console.php | 1320 ++++++++--------- devices.php | 32 + get_matrix_token.php | 66 +- historico.php | 312 ++-- homeserver.yaml | 102 +- matrix_admin.php | 558 +++---- proxmox.php | 211 +++ publish.php | 62 +- save_note.php | 30 +- send_ping.php | 50 +- vendor/php-mqtt/client/.github/release.yml | 50 +- vpn.php | 228 +++ 24 files changed, 2318 insertions(+), 1916 deletions(-) create mode 100644 proxmox.php create mode 100644 vpn.php diff --git a/api/contadores_json.php b/api/contadores_json.php index 343d73c..b7dd970 100644 --- a/api/contadores_json.php +++ b/api/contadores_json.php @@ -1,35 +1,35 @@ -query(" - SELECT uid, note, entradas, saidas, online, blocked, - current_broker, rssi, heap, uptime, - TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago - FROM contadores - ORDER BY online DESC, uid ASC -")->fetchAll(); - -// Totais -$sumEntradas = 0; -$sumSaidas = 0; -$onlineCount = 0; -$offlineCount = 0; - -foreach ($rows as $r) { - $sumEntradas += (int)$r['entradas']; - $sumSaidas += (int)$r['saidas']; - if ($r['online'] == 1) $onlineCount++; - else $offlineCount++; -} -header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); -header("Pragma: no-cache"); -header("Expires: 0"); - -echo json_encode([ - "rows" => $rows, - "online" => $onlineCount, - "offline" => $offlineCount, - "entradas" => $sumEntradas, - "saidas" => $sumSaidas, -]); +query(" + SELECT uid, note, entradas, saidas, online, blocked, + current_broker, rssi, heap, uptime, + TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago + FROM contadores + ORDER BY online DESC, uid ASC +")->fetchAll(); + +// Totais +$sumEntradas = 0; +$sumSaidas = 0; +$onlineCount = 0; +$offlineCount = 0; + +foreach ($rows as $r) { + $sumEntradas += (int)$r['entradas']; + $sumSaidas += (int)$r['saidas']; + if ($r['online'] == 1) $onlineCount++; + else $offlineCount++; +} +header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); +header("Pragma: no-cache"); +header("Expires: 0"); + +echo json_encode([ + "rows" => $rows, + "online" => $onlineCount, + "offline" => $offlineCount, + "entradas" => $sumEntradas, + "saidas" => $sumSaidas, +]); diff --git a/api/listener_recovery.php b/api/listener_recovery.php index 763286d..6d8186f 100644 --- a/api/listener_recovery.php +++ b/api/listener_recovery.php @@ -50,29 +50,12 @@ $db = new PDO( ] ); -// ===================================================== -// RECOVERY MQTT -// ===================================================== - -$recoveryHost = 'mqtt-recovery.xupas.mywire.org'; -$recoveryPort = 8884; - -$recoveryUser = 'recovery'; -$recoveryPass = 'recovery123'; - -// ===================================================== -// MAIN MQTT -// ===================================================== - -$mainHost = 'mqtt.xupas.mywire.org'; -$mainPort = 8883; - -// ===================================================== -// CERT -// ===================================================== - -$certPath = '/etc/mosquitto/certs/chain.pem'; // cert Let's Encrypt do broker principal -$certUrl = 'https://mqtt.xupas.mywire.org/cert.pem'; +// ─── migração: coluna auth_nonce_at (idempotente) ────── +$db->exec(" + ALTER TABLE contadores + ADD COLUMN IF NOT EXISTS + auth_nonce_at DATETIME NULL DEFAULT NULL +"); // ===================================================== // GLOBAL SECRET @@ -81,28 +64,92 @@ $certUrl = 'https://mqtt.xupas.mywire.org/cert.pem'; $authSecret = 'K82A91F0'; // ===================================================== -// MQTT SETTINGS +// CA CERT mqtt-recovery.xpto.ooguy.com (self-signed) // ===================================================== -$settings = (new ConnectionSettings()) - ->setUsername($recoveryUser) - ->setPassword($recoveryPass) - ->setUseTls(true) - ->setTlsSelfSignedAllowed(true) - ->setKeepAliveInterval(30) - ->setConnectTimeout(5) - ->setSocketTimeout(1) - ->setReconnectAutomatically(true); +$recoveryXptoCaCert = <<<'EOT' +-----BEGIN CERTIFICATE----- +MIIDuTCCAqGgAwIBAgIUass5a34ekbqg0iaygCtiGRiSFHIwDQYJKoZIhvcNAQEL +BQAwbDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDElMCMGA1UEAwwcbXF0dC1yZWNvdmVy +eS54cHRvLm9vZ3V5LmNvbTAeFw0yNjA1MjYxMjM1NDFaFw0zNjA1MjMxMjM1NDFa +MGwxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ +bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxJTAjBgNVBAMMHG1xdHQtcmVjb3Zlcnku +eHB0by5vb2d1eS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDe +2uCWyHP/xng8DlL4SgqlXq9YbZQw/XWIoNDcpCPAirSKW/d6RNoIa4/UoUtOVfUq +vN7JgAi+Ovz86q+aagYdVgtU/pIsj/U3/uwZ8Ga2g/AWCD2WZawE/SfpIoape6PM +EnvSjFpyTZCLcOh6xUIdqH2/RcI7WJvgBBFkocZuP12zJD4jAIZC41+LpjUQQ6rM +DANhmUYD2fVXdQ8f9hWuKH0inal+2eHtY6n4oL3RZdaSQNQz0j5QKeVfMuAKrMNu +JeUEYku7Exvr6slaeoEVqlj7UwfwCPNernOvleB5y9dMKDNbXeHAFBdrPMQrgAK7 +KZvNq/5caqpzdxZfskJFAgMBAAGjUzBRMB0GA1UdDgQWBBThMl6pM+mkvCeVFwkX +NTTnE0R2jDAfBgNVHSMEGDAWgBThMl6pM+mkvCeVFwkXNTTnE0R2jDAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDDSbFkl1BIotX6dREzyYh0H4dF +NTU3zAayZnd4k33oIgpVnpoVd9PBwzRiRc8PuTiQv6iF2vsmJY/xghNp7UlgHo4k +UEuF15F24fCj9Xqpbm1EgDGcDzwOsS+RD91bIXjt5U4m8S1meoJ9+xxK6LanUwp8 +FSgxiw+NGcaDMH4Dc6U6Qvxo3ycIkaJQ+SuK72RW0naC9oMp+Yq0otUjDpUkkmP9 +fNex9ArIrKPhWqWBRnImVStfY/1nzg9Eh7bS6k+qpVIEfvKVLdt2WfWDDtazaJpq +eJSAR5CXLy3G8OKZpcSe0YXb2NkDX/R88B7z9uGD6Zz/lHE9Zot+9JS6tcvZ +-----END CERTIFICATE----- +EOT; + +$recoveryXptoCaFile = '/etc/mosquitto/certs/recovery-xpto-ca.pem'; + +if ( + !file_exists($recoveryXptoCaFile) || + trim((string) file_get_contents($recoveryXptoCaFile)) !== trim($recoveryXptoCaCert) +) { + file_put_contents($recoveryXptoCaFile, $recoveryXptoCaCert); + logmsg("CA cert escrito: $recoveryXptoCaFile"); +} // ===================================================== -// MQTT CLIENT +// BROKER PAIRS +// ===================================================== +// +// Cada par agrupa: +// recvHost/Port/User/Pass → recovery broker (onde o ESP se liga quando o principal cai) +// recvCaFile → CA para verificar o cert TLS do recovery broker +// null = usar CA do sistema (Let's Encrypt) +// selfSigned → true = aceitar certs self-signed (TlsSelfSignedAllowed) +// mainHost/Port → main broker (destino do SWITCH_PRIMARY) +// certPath → cert do main broker guardado localmente (enviado ao ESP) +// certUrl → URL para refrescar o cert do main broker +// // ===================================================== -$mqtt = new MqttClient( - $recoveryHost, - $recoveryPort, - 'listener-recovery-' . getmypid() -); +$brokerPairs = [ + + // ── Remoto — Let's Encrypt ───────────────────── + [ + 'label' => 'remote', + 'recvHost' => 'mqtt-recovery.xupas.mywire.org', + 'recvPort' => 8884, + 'recvUser' => 'recovery', + 'recvPass' => 'recovery123', + 'recvCaFile' => null, // Let's Encrypt — CA do sistema + 'selfSigned' => true, // mantém comportamento original + 'mainHost' => 'mqtt.xupas.mywire.org', + 'mainPort' => 8883, + 'certPath' => '/etc/mosquitto/certs/device-ca.pem', + 'certUrl' => 'https://mqtt.xupas.mywire.org/cert.pem', + ], + + // ── Local — self-signed ──────────────────────── + [ + 'label' => 'local-selfsigned', + 'recvHost' => 'mqtt-recovery.xpto.ooguy.com', + 'recvPort' => 8884, + 'recvUser' => 'xupa', + 'recvPass' => 'xupa', + 'recvCaFile' => $recoveryXptoCaFile, + 'selfSigned' => true, + 'mainHost' => 'mqtt.xpto.ooguy.com', + 'mainPort' => 8883, + 'certPath' => '/etc/mosquitto/certs/device-ca.pem', + 'certUrl' => 'https://mqtt.xpto.ooguy.com/cert.pem', + ], + +]; // ===================================================== // MAIN BROKER CHECK @@ -206,18 +253,41 @@ function send_cert_chunks( } // ===================================================== -// SUBSCRIPTIONS +// MAKE SETTINGS // ===================================================== -$subscribe = function () use ( - $mqtt, - $db, - $certPath, - $certUrl, - $mainHost, - $mainPort, - $authSecret -) { +function make_settings(array $pair): ConnectionSettings +{ + $s = (new ConnectionSettings()) + ->setUsername($pair['recvUser']) + ->setPassword($pair['recvPass']) + ->setUseTls(true) + ->setTlsSelfSignedAllowed($pair['selfSigned']) + ->setKeepAliveInterval(30) + ->setConnectTimeout(5) + ->setSocketTimeout(1) + ->setReconnectAutomatically(true); + + if ($pair['recvCaFile'] !== null) { + $s = $s->setTlsCertificateAuthorityFile($pair['recvCaFile']); + } + + return $s; +} + +// ===================================================== +// SUBSCRIBE BROKER +// ===================================================== + +function subscribe_broker( + MqttClient $mqtt, + PDO $db, + array $pair, + string $authSecret +): void { + + $label = $pair['label']; + $certPath = $pair['certPath']; // ================================================= // AUTH @@ -226,7 +296,7 @@ $subscribe = function () use ( $mqtt->subscribe( 'esp/+/auth', function (string $topic, string $msg) - use ($db, $mqtt, $authSecret) + use ($db, $mqtt, $authSecret, $label) { $uid = explode('/', $topic)[1] ?? null; @@ -235,17 +305,12 @@ $subscribe = function () use ( return; } - logmsg("AUTH RX $uid => $msg"); + logmsg("[$label] AUTH RX $uid => $msg"); $json = json_decode($msg, true); if (!$json) { - - logmsg( - "JSON AUTH inválido", - LOG_ERROR - ); - + logmsg("[$label] JSON AUTH inválido", LOG_ERROR); return; } @@ -255,41 +320,62 @@ $subscribe = function () use ( if (($json['cmd'] ?? '') === 'HELLO') { - $nonce = - strtoupper( - bin2hex(random_bytes(16)) - ); - + // ─── Idempotência: evitar AUTH_CHALLENGE duplicado ─ + // Quando o listener reinicia, o broker pode reentrogar + // mensagens QoS=1 não ACKadas pela instância anterior. + // Se já existe um nonce recente (< 30 s) reutilizamo-lo + // em vez de gerar um novo e sobrescrever o DB. $stmt = $db->prepare(" - INSERT INTO contadores - (uid, auth_nonce, last_seen, online, blocked) - VALUES - (:uid, :nonce, NOW(), 1, 1) - ON DUPLICATE KEY UPDATE - auth_nonce=:nonce, - last_seen=NOW(), - online=1 + SELECT auth_nonce, auth_nonce_at + FROM contadores + WHERE uid=:uid + LIMIT 1 "); - /* novos ESPs entram com blocked=1 (pendente aprovação) - ESPs existentes mantêm o blocked actual */ + $stmt->execute(['uid' => $uid]); + $existing = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->execute([ - 'uid' => $uid, - 'nonce' => $nonce - ]); + $existingNonce = $existing['auth_nonce'] ?? null; + $nonceAge = $existing['auth_nonce_at'] + ? (time() - strtotime($existing['auth_nonce_at'])) + : PHP_INT_MAX; + + if ($existingNonce && $nonceAge < 30) { + // Nonce recente — reutilizar sem tocar no DB + $nonce = $existingNonce; + logmsg("[$label] AUTH_CHALLENGE reutilizado para $uid (age {$nonceAge}s)"); + } else { + $nonce = strtoupper(bin2hex(random_bytes(16))); + + $stmt = $db->prepare(" + INSERT INTO contadores + (uid, auth_nonce, auth_nonce_at, last_seen, online, blocked) + VALUES + (:uid, :nonce, NOW(), NOW(), 1, 1) + ON DUPLICATE KEY UPDATE + auth_nonce=:nonce, + auth_nonce_at=NOW(), + last_seen=NOW(), + online=1 + "); + /* novos ESPs entram com blocked=1 (pendente aprovação) + ESPs existentes mantêm o blocked actual */ + + $stmt->execute([ + 'uid' => $uid, + 'nonce' => $nonce, + ]); + } $mqtt->publish( "esp/$uid/cmd", json_encode([ - 'cmd' => 'AUTH_CHALLENGE', - 'nonce' => $nonce + 'cmd' => 'AUTH_CHALLENGE', + 'nonce' => $nonce, ]), 1 ); - logmsg( - "AUTH_CHALLENGE enviado para $uid" - ); + logmsg("[$label] AUTH_CHALLENGE enviado para $uid"); return; } @@ -306,12 +392,7 @@ $subscribe = function () use ( ); if (!$hash) { - - logmsg( - "AUTH sem hash", - LOG_ERROR - ); - + logmsg("[$label] AUTH sem hash", LOG_ERROR); return; } @@ -326,29 +407,17 @@ $subscribe = function () use ( 'uid' => $uid ]); - $row = - $stmt->fetch(PDO::FETCH_ASSOC); + $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row) { - - logmsg( - "UID desconhecido: $uid", - LOG_ERROR - ); - + logmsg("[$label] UID desconhecido: $uid", LOG_ERROR); return; } - $nonce = - $row['auth_nonce'] ?? ''; + $nonce = $row['auth_nonce'] ?? ''; if (!$nonce) { - - logmsg( - "Nonce vazio para $uid", - LOG_ERROR - ); - + logmsg("[$label] Nonce vazio para $uid", LOG_ERROR); return; } @@ -366,7 +435,10 @@ $subscribe = function () use ( // verificar se está aprovado if ((int)($row['blocked'] ?? 1) === 1) { - logmsg("AUTH PENDENTE/BLOQUEADO $uid — aguarda aprovação", LOG_ERROR); + logmsg( + "[$label] AUTH PENDENTE/BLOQUEADO $uid — aguarda aprovação", + LOG_ERROR + ); $mqtt->publish( "esp/$uid/cmd", @@ -377,12 +449,13 @@ $subscribe = function () use ( return; } - logmsg("AUTH OK $uid"); + logmsg("[$label] AUTH OK $uid"); $stmt = $db->prepare(" UPDATE contadores SET auth_nonce=NULL, + auth_nonce_at=NULL, auth_ok=1, online=1, last_seen=NOW() @@ -403,18 +476,10 @@ $subscribe = function () use ( } else { - logmsg( - "AUTH FAIL $uid", - LOG_ERROR - ); - - $mqtt->publish( - "esp/$uid/cmd", - json_encode([ - 'cmd' => 'AUTH_FAIL' - ]), - 1 - ); + // Hash nao bate: quase sempre e cross-talk - o device respondeu + // ao nonce do OUTRO listener (VPS). NAO mandar AUTH_FAIL (causava + // boot-loop). So o listener cujo nonce bate manda AUTH_OK. + logmsg("[$label] AUTH hash mismatch $uid - ignorado (cross-talk)", LOG_ERROR); } return; @@ -433,9 +498,7 @@ $subscribe = function () use ( string $msg ) use ( $db, - $mqtt, - $mainHost, - $mainPort + $label ) { $uid = explode('/', $topic)[1] ?? null; @@ -474,41 +537,7 @@ $subscribe = function () use ( 'uid' => $uid ]); - logmsg( - "RECOVERY STATUS $uid => $msg" - ); - - static $lastCheck = 0; - - if (time() - $lastCheck >= 15) { - - $lastCheck = time(); - - logmsg("CHECK broker"); - - if ( - main_broker_online( - $mainHost, - $mainPort - ) - ) { - - logmsg( - "MAIN broker online" - ); - - send_switch_primary( - $mqtt, - $db - ); - - } else { - - logmsg( - "MAIN broker offline" - ); - } - } + logmsg("[$label] RECOVERY STATUS $uid => $msg"); }, 0 @@ -523,18 +552,15 @@ $subscribe = function () use ( function ( string $topic, string $msg - ) { + ) use ($label) { - $uid = - explode('/', $topic)[1] ?? null; + $uid = explode('/', $topic)[1] ?? null; if (!$uid) { return; } - logmsg( - "RECOVERY EVENT $uid => $msg" - ); + logmsg("[$label] RECOVERY EVENT $uid => $msg"); }, 0 @@ -552,100 +578,165 @@ $subscribe = function () use ( ) use ( $mqtt, $certPath, - $certUrl + $label ) { - $uid = - explode('/', $topic)[1] ?? null; + $uid = explode('/', $topic)[1] ?? null; if (!$uid) { return; } - logmsg("CERT_REQUEST de $uid → chunks"); + logmsg("[$label] CERT_REQUEST de $uid → chunks"); send_cert_chunks($mqtt, $uid, $certPath); }, 1 ); -}; +} // ===================================================== // MAIN LOOP // ===================================================== +$clients = []; // label => MqttClient +$nextCheck = []; // label => timestamp da próxima verificação do main broker +$lastConnectAttempt = []; // label => timestamp da última tentativa de ligação + while (true) { - try { + // ── connect / reconnect ──────────────────────── - logmsg( - "Ligar recovery $recoveryHost:$recoveryPort" - ); + foreach ($brokerPairs as $pair) { - $mqtt->connect($settings, false); - - $subscribe(); - - logmsg( - "RECOVERY ligado e subscrito" - ); - - $nextMainCheck = time() + 10; - $loopStartedAt = microtime(true); - - while ($mqtt->isConnected()) { - - $mqtt->loopOnce($loopStartedAt, true, 100000); - - if (time() >= $nextMainCheck) { - - $nextMainCheck = time() + 15; - - logmsg( - "LOOP recovery", - LOG_DEBUG - ); - - if ( - main_broker_online( - $mainHost, - $mainPort - ) - ) { - - logmsg( - "MAIN broker online" - ); - - logmsg("Mandar switch_primary"); - - send_switch_primary( - $mqtt, - $db - ); - - } else { - - logmsg( - "MAIN broker ainda offline", - LOG_DEBUG - ); - } - } + $label = $pair['label']; + if ( + isset($clients[$label]) && + $clients[$label]->isConnected() + ) { + continue; } - $mqtt->disconnect(); + // throttle: não tentar mais do que 1x / 5 s por broker + if (time() < ($lastConnectAttempt[$label] ?? 0) + 5) { + continue; + } - } catch (Throwable $e) { + $lastConnectAttempt[$label] = time(); - logmsg( - "ERRO recovery: " . - $e->getMessage(), - LOG_ERROR - ); + try { - sleep(5); + logmsg( + "Ligar recovery [{$label}] " . + "{$pair['recvHost']}:{$pair['recvPort']}" + ); + + $clients[$label] = new MqttClient( + $pair['recvHost'], + $pair['recvPort'], + 'listener-recovery-' . $label . '-' . getmypid() + ); + + $clients[$label]->connect( + make_settings($pair), + false + ); + + subscribe_broker( + $clients[$label], + $db, + $pair, + $authSecret + ); + + // primeira verificação do main broker após 10 s + $nextCheck[$label] = time() + 10; + + logmsg("[{$label}] ligado e subscrito"); + + } catch (Throwable $e) { + + logmsg( + "[{$label}] ERRO connect: " . $e->getMessage(), + LOG_ERROR + ); + + unset($clients[$label]); + } } -} \ No newline at end of file + + if (empty($clients)) { + sleep(5); + continue; + } + + // ── drive all clients (50 ms cada) ──────────── + + $loopStartedAt = microtime(true); + + foreach ($clients as $label => $mqtt) { + + try { + + $mqtt->loopOnce($loopStartedAt, true, 50000); + + } catch (Throwable $e) { + + logmsg( + "[{$label}] ERRO loop: " . $e->getMessage(), + LOG_ERROR + ); + + try { + $mqtt->disconnect(); + } catch (Throwable $_) {} + + unset($clients[$label]); + } + } + + // ── periodic main broker check per pair ──────── + + $now = time(); + + foreach ($brokerPairs as $pair) { + + $label = $pair['label']; + + if ( + !isset($clients[$label]) || + !$clients[$label]->isConnected() + ) { + continue; + } + + if ($now < ($nextCheck[$label] ?? 0)) { + continue; + } + + $nextCheck[$label] = $now + 15; + + logmsg("[{$label}] CHECK main broker", LOG_DEBUG); + + if ( + main_broker_online( + $pair['mainHost'], + $pair['mainPort'] + ) + ) { + + logmsg("[{$label}] MAIN broker online → SWITCH_PRIMARY"); + + send_switch_primary($clients[$label], $db); + + } else { + + logmsg( + "[{$label}] MAIN broker ainda offline", + LOG_DEBUG + ); + } + } +} diff --git a/api/menu.php b/api/menu.php index b5698f0..6e32ab2 100644 --- a/api/menu.php +++ b/api/menu.php @@ -1,163 +1,3 @@ &1"; - - $outputVPN = shell_exec($sshCmd); - } else { - $outputVPN = "❌ Comando VPN inválido."; - } -} -?> - - - - - Menu Principal - - - - - - -
- -

- Bem-vindo, 😎 -

- - - - - -
-

🌍 Controlo da VPN

- - - - -
-
-
- - - - - -
-

© Xupas Systems | Todos os direitos reservados

-
- -
- - - +header("Location: /vpn.php"); +exit; diff --git a/api/mqtt.php b/api/mqtt.php index b87edf9..f75f5e1 100644 --- a/api/mqtt.php +++ b/api/mqtt.php @@ -1,11 +1,11 @@ - /dev/null 2>&1"); -} + /dev/null 2>&1"); +} diff --git a/api/reset_parciais.php b/api/reset_parciais.php index 1e213e5..65f9413 100644 --- a/api/reset_parciais.php +++ b/api/reset_parciais.php @@ -1,44 +1,44 @@ - PDO::ERRMODE_EXCEPTION, - ]); - - // garantir colunas (ignora se já existirem) - $db->exec("ALTER TABLE contadores ADD COLUMN IF NOT EXISTS entradas_total INT NOT NULL DEFAULT 0"); - $db->exec("ALTER TABLE contadores ADD COLUMN IF NOT EXISTS saidas_total INT NOT NULL DEFAULT 0"); - - $uid = $_POST['uid'] ?? ''; - if ($uid === '') { - header("Location: /console.php?ok=err"); - exit; - } - - $stmt = $db->prepare(" - UPDATE contadores - SET entradas_total = COALESCE(entradas_total,0) + COALESCE(entradas,0), - saidas_total = COALESCE(saidas_total,0) + COALESCE(saidas,0), - entradas = 0, - saidas = 0 - WHERE uid = :uid - "); - $stmt->execute(['uid' => $uid]); - - header("Location: /console.php?ok=line"); - exit; - -} catch (Throwable $e) { - header("Location: /console.php?ok=err"); - exit; -} + PDO::ERRMODE_EXCEPTION, + ]); + + // garantir colunas (ignora se já existirem) + $db->exec("ALTER TABLE contadores ADD COLUMN IF NOT EXISTS entradas_total INT NOT NULL DEFAULT 0"); + $db->exec("ALTER TABLE contadores ADD COLUMN IF NOT EXISTS saidas_total INT NOT NULL DEFAULT 0"); + + $uid = $_POST['uid'] ?? ''; + if ($uid === '') { + header("Location: /console.php?ok=err"); + exit; + } + + $stmt = $db->prepare(" + UPDATE contadores + SET entradas_total = COALESCE(entradas_total,0) + COALESCE(entradas,0), + saidas_total = COALESCE(saidas_total,0) + COALESCE(saidas,0), + entradas = 0, + saidas = 0 + WHERE uid = :uid + "); + $stmt->execute(['uid' => $uid]); + + header("Location: /console.php?ok=line"); + exit; + +} catch (Throwable $e) { + header("Location: /console.php?ok=err"); + exit; +} diff --git a/api/reset_pass.php b/api/reset_pass.php index 0f79f08..1a4c1eb 100644 --- a/api/reset_pass.php +++ b/api/reset_pass.php @@ -1,11 +1,11 @@ - PDO::ERRMODE_EXCEPTION -]); - -$newPassword = password_hash("admin123", PASSWORD_DEFAULT); - -$stmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE username = 'admin'"); -$stmt->execute([$newPassword]); - -echo "✅ Password de admin alterada para admin123"; + PDO::ERRMODE_EXCEPTION +]); + +$newPassword = password_hash("admin123", PASSWORD_DEFAULT); + +$stmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE username = 'admin'"); +$stmt->execute([$newPassword]); + +echo "✅ Password de admin alterada para admin123"; diff --git a/api/send_cmd.php b/api/send_cmd.php index c6a6dcc..b0ae6cf 100644 --- a/api/send_cmd.php +++ b/api/send_cmd.php @@ -1,34 +1,34 @@ -'invalid_request']); - exit; -} - -$uid = $data['uid']; -$cmd = $data['cmd']; -$value = $data['value'] ?? null; - -// ----------------------- MQTT ----------------------- -$topic = "esp/$uid/cmd"; - -$msg = ['cmd' => $cmd]; -if ($value !== null) $msg['value'] = $value; - -mqtt_publish($topic, json_encode($msg, JSON_UNESCAPED_SLASHES)); - -// ----------------------- BD ------------------------- -if ($cmd === 'BLOCK') { - $stmt = $pdo->prepare("UPDATE contadores SET blocked = ? WHERE uid = ?"); - $stmt->execute([intval($value), $uid]); -} - -echo json_encode(['ok'=>1]); +'invalid_request']); + exit; +} + +$uid = $data['uid']; +$cmd = $data['cmd']; +$value = $data['value'] ?? null; + +// ----------------------- MQTT ----------------------- +$topic = "esp/$uid/cmd"; + +$msg = ['cmd' => $cmd]; +if ($value !== null) $msg['value'] = $value; + +mqtt_publish($topic, json_encode($msg, JSON_UNESCAPED_SLASHES)); + +// ----------------------- BD ------------------------- +if ($cmd === 'BLOCK') { + $stmt = $pdo->prepare("UPDATE contadores SET blocked = ? WHERE uid = ?"); + $stmt->execute([intval($value), $uid]); +} + +echo json_encode(['ok'=>1]); diff --git a/api/sniff_once.php b/api/sniff_once.php index 466c068..7220989 100644 --- a/api/sniff_once.php +++ b/api/sniff_once.php @@ -1,23 +1,23 @@ -setUsername('xupa')->setPassword('xupa'); -$mqtt->connect($settings, true); - -$mqtt->subscribe('esp/+/event', function($topic, $message) use (&$data, $mqtt) { - $data[] = ['topic'=>$topic, 'message'=>$message]; - if (count($data) >= 5) { - $mqtt->interrupt(); // para o loop depois de 5 msg - } -}, 0); - -$mqtt->loop(true); - -header('Content-Type: application/json'); -echo json_encode($data, JSON_PRETTY_PRINT); +setUsername('xupa')->setPassword('xupa'); +$mqtt->connect($settings, true); + +$mqtt->subscribe('esp/+/event', function($topic, $message) use (&$data, $mqtt) { + $data[] = ['topic'=>$topic, 'message'=>$message]; + if (count($data) >= 5) { + $mqtt->interrupt(); // para o loop depois de 5 msg + } +}, 0); + +$mqtt->loop(true); + +header('Content-Type: application/json'); +echo json_encode($data, JSON_PRETTY_PRINT); diff --git a/api/validar.php b/api/validar.php index 854ddfa..54b4d15 100644 --- a/api/validar.php +++ b/api/validar.php @@ -33,7 +33,7 @@ try { $_SESSION['username'] = $user['username']; // Redireciona para o menu principal - header("Location: /api/menu.php"); + header("Location: /painel.php"); exit; } catch (Exception $e) { diff --git a/api/vendor/php-mqtt/client/.github/release.yml b/api/vendor/php-mqtt/client/.github/release.yml index 374affd..2cd9ffd 100644 --- a/api/vendor/php-mqtt/client/.github/release.yml +++ b/api/vendor/php-mqtt/client/.github/release.yml @@ -1,25 +1,25 @@ -changelog: - exclude: - labels: - - ignore-for-release - authors: - - octocat - categories: - - title: Added - labels: - - enhancement - - title: Deprecated - labels: - - deprecated - - title: Removed - labels: - - removed - - title: Fixed - labels: - - bug - - title: Security - labels: - - security - - title: Changed - labels: - - "*" +changelog: + exclude: + labels: + - ignore-for-release + authors: + - octocat + categories: + - title: Added + labels: + - enhancement + - title: Deprecated + labels: + - deprecated + - title: Removed + labels: + - removed + - title: Fixed + labels: + - bug + - title: Security + labels: + - security + - title: Changed + labels: + - "*" diff --git a/comandosbck.php b/comandosbck.php index 6ee202e..8f6232a 100644 --- a/comandosbck.php +++ b/comandosbck.php @@ -1,55 +1,55 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -} catch (PDOException $e) { - die("Erro DB: " . $e->getMessage()); -} - -$stmt = $db->query("SELECT * FROM comandos ORDER BY id DESC LIMIT 50"); -$comandos = $stmt->fetchAll(PDO::FETCH_ASSOC); -?> - - - - - Histórico de Comandos - - - -

Histórico de Comandos

- - - - - - - - - - - - - - - - - - - -
IDUIDCMDParamsStatusData
-

⬅️ Voltar

- - +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die("Erro DB: " . $e->getMessage()); +} + +$stmt = $db->query("SELECT * FROM comandos ORDER BY id DESC LIMIT 50"); +$comandos = $stmt->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + Histórico de Comandos + + + +

Histórico de Comandos

+ + + + + + + + + + + + + + + + + + + +
IDUIDCMDParamsStatusData
+

⬅️ Voltar

+ + diff --git a/config.php b/config.php index 203fcc2..d879f37 100644 --- a/config.php +++ b/config.php @@ -1,11 +1,11 @@ - 'mqtt.xupas.mywire.org', - 'port' => 8883, - 'clientId' => 'php-web-' . bin2hex(random_bytes(3)), - 'username' => 'xupa', - 'password' => 'xupa', - 'clean' => true, - 'keepalive'=> 60, - 'timeout' => 5, -]; + 'mqtt.xupas.mywire.org', + 'port' => 8883, + 'clientId' => 'php-web-' . bin2hex(random_bytes(3)), + 'username' => 'xupa', + 'password' => 'xupa', + 'clean' => true, + 'keepalive'=> 60, + 'timeout' => 5, +]; diff --git a/console.php b/console.php index 784cac5..99aa049 100644 --- a/console.php +++ b/console.php @@ -1,661 +1,661 @@ -prepare(" - UPDATE contadores - SET note = ? - WHERE uid = ? - "); - - $stmt->execute([$note, $uid]); - - header("Location: console.php"); - exit; -} - -// ===================================================== -// REMOVER -// ===================================================== - -if (isset($_GET['remove'])) { - - $uid = $_GET['remove']; - - $pdo->prepare(" - DELETE FROM contadores - WHERE uid = ? - ")->execute([$uid]); - - header("Location: console.php"); - exit; -} - -// ===================================================== -// BLOQUEAR / DESBLOQUEAR -// ===================================================== - -if (isset($_GET['toggle'])) { - - $uid = $_GET['toggle']; - - $pdo->prepare(" - UPDATE contadores - SET blocked = 1 - blocked - WHERE uid = ? - ")->execute([$uid]); - - header("Location: console.php"); - exit; -} - -// ===================================================== -// SORT -// ===================================================== - -$sort = $_GET['sort'] ?? 'state'; - -$orderSql = ($sort === 'uid') - ? "ORDER BY uid ASC" - : "ORDER BY online DESC, uid ASC"; - -// ===================================================== -// QUERY -// ===================================================== - -$sql = " - SELECT - uid, - note, - entradas, - saidas, - online, - blocked, - current_broker, - rssi, - heap, - uptime, - last_seen, - TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago - FROM contadores - $orderSql -"; - -$stmt = $pdo->query($sql); - -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - -// ===================================================== -// TOTAIS -// ===================================================== - -$sumEntradas = 0; -$sumSaidas = 0; - -$onlineCount = 0; -$offlineCount = 0; - -foreach ($rows as $r) { - - $sumEntradas += (int)$r['entradas']; - $sumSaidas += (int)$r['saidas']; - - if ((int)$r['online'] === 1) { - $onlineCount++; - } else { - $offlineCount++; - } -} -?> - - - - - - - -MQTT Swarm Console - - - - - - - - - -
- -

- 🌋 MQTT SWARM CONSOLE -

- - -
- -
-
-

-
- 🟢 ONLINE -
-
-
- -
-
-

-
- 🔴 OFFLINE -
-
-
- -
-
-

-
- 💰 ENTRADAS -
-
-
- -
-
-

-
- 💸 SAÍDAS -
-
-
- -
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - BLOQUEADO - '; - - } elseif ($online) { - - $state = - ' - ONLINE - '; - - } else { - - $state = - ' - OFFLINE - '; - } - - if (($r['current_broker'] ?? '') === 'recovery') { - - $broker = - ' - RECOVERY - '; - - } else { - - $broker = - ' - PRIMARY - '; - } - - $ago = (int)$r['ago']; - - $lastSeen = ($ago < 60) - ? "{$ago}s" - : round($ago / 60) . "m"; - - $pulseClass = - ($ago <= 2 && $online) - ? "pulse-cell" - : ""; - - ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UIDNOTAENTRADASSAÍDASESTADOBROKERRSSIHEAPUPTIMEÚLTIMOAÇÕES
- - - -
- - - -
- - - - - -
- -
- -
- - - - - - - - - - - - - - - -
- -
- -
- -
- - - - +prepare(" + UPDATE contadores + SET note = ? + WHERE uid = ? + "); + + $stmt->execute([$note, $uid]); + + header("Location: console.php"); + exit; +} + +// ===================================================== +// REMOVER +// ===================================================== + +if (isset($_GET['remove'])) { + + $uid = $_GET['remove']; + + $pdo->prepare(" + DELETE FROM contadores + WHERE uid = ? + ")->execute([$uid]); + + header("Location: console.php"); + exit; +} + +// ===================================================== +// BLOQUEAR / DESBLOQUEAR +// ===================================================== + +if (isset($_GET['toggle'])) { + + $uid = $_GET['toggle']; + + $pdo->prepare(" + UPDATE contadores + SET blocked = 1 - blocked + WHERE uid = ? + ")->execute([$uid]); + + header("Location: console.php"); + exit; +} + +// ===================================================== +// SORT +// ===================================================== + +$sort = $_GET['sort'] ?? 'state'; + +$orderSql = ($sort === 'uid') + ? "ORDER BY uid ASC" + : "ORDER BY online DESC, uid ASC"; + +// ===================================================== +// QUERY +// ===================================================== + +$sql = " + SELECT + uid, + note, + entradas, + saidas, + online, + blocked, + current_broker, + rssi, + heap, + uptime, + last_seen, + TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago + FROM contadores + $orderSql +"; + +$stmt = $pdo->query($sql); + +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// ===================================================== +// TOTAIS +// ===================================================== + +$sumEntradas = 0; +$sumSaidas = 0; + +$onlineCount = 0; +$offlineCount = 0; + +foreach ($rows as $r) { + + $sumEntradas += (int)$r['entradas']; + $sumSaidas += (int)$r['saidas']; + + if ((int)$r['online'] === 1) { + $onlineCount++; + } else { + $offlineCount++; + } +} +?> + + + + + + + +MQTT Swarm Console + + + + + + + + + +
+ +

+ 🌋 MQTT SWARM CONSOLE +

+ + +
+ +
+
+

+
+ 🟢 ONLINE +
+
+
+ +
+
+

+
+ 🔴 OFFLINE +
+
+
+ +
+
+

+
+ 💰 ENTRADAS +
+
+
+ +
+
+

+
+ 💸 SAÍDAS +
+
+
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + BLOQUEADO + '; + + } elseif ($online) { + + $state = + ' + ONLINE + '; + + } else { + + $state = + ' + OFFLINE + '; + } + + if (($r['current_broker'] ?? '') === 'recovery') { + + $broker = + ' + RECOVERY + '; + + } else { + + $broker = + ' + PRIMARY + '; + } + + $ago = (int)$r['ago']; + + $lastSeen = ($ago < 60) + ? "{$ago}s" + : round($ago / 60) . "m"; + + $pulseClass = + ($ago <= 2 && $online) + ? "pulse-cell" + : ""; + + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UIDNOTAENTRADASSAÍDASESTADOBROKERRSSIHEAPUPTIMEÚLTIMOAÇÕES
+ + + +
+ + + +
+ + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/devices.php b/devices.php index de2e178..c186ab4 100644 --- a/devices.php +++ b/devices.php @@ -122,6 +122,7 @@ $pending = $pdo->query(" $approved = $pdo->query(" SELECT uid, note, last_seen, online, blocked, auth_ok, current_broker, + COALESCE(brightness, 128) AS brightness, TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago FROM contadores WHERE blocked=0 OR auth_ok=1 @@ -208,6 +209,7 @@ body { background:#0b0b0b; color:#e0e0e0; font-family:Arial,sans-serif; } ESTADO BROKER ÚLTIMO + BRILHO AÇÕES @@ -231,12 +233,27 @@ body { background:#0b0b0b; color:#e0e0e0; font-family:Arial,sans-serif; } ? 'RECOVERY' : 'PRIMARY'; ?> + + +
+ > + + + +
+
+ + diff --git a/get_matrix_token.php b/get_matrix_token.php index 01cceeb..f0b4d1b 100644 --- a/get_matrix_token.php +++ b/get_matrix_token.php @@ -1,33 +1,33 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - - $stmt = $pdo->prepare("SELECT token_plain FROM api_tokens WHERE label = ? AND revoked = 0 ORDER BY id DESC LIMIT 1"); - $stmt->execute(['matrix-admin']); - $row = $stmt->fetch(); - - if (!$row || empty($row['token_plain'])) { - http_response_code(404); - echo json_encode(["error" => "Token não encontrado"]); - exit; - } - - echo json_encode(["token" => $row["token_plain"]]); -} catch (Throwable $e) { - http_response_code(500); - echo json_encode(["error" => $e->getMessage()]); -} + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + + $stmt = $pdo->prepare("SELECT token_plain FROM api_tokens WHERE label = ? AND revoked = 0 ORDER BY id DESC LIMIT 1"); + $stmt->execute(['matrix-admin']); + $row = $stmt->fetch(); + + if (!$row || empty($row['token_plain'])) { + http_response_code(404); + echo json_encode(["error" => "Token não encontrado"]); + exit; + } + + echo json_encode(["token" => $row["token_plain"]]); +} catch (Throwable $e) { + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); +} diff --git a/historico.php b/historico.php index b482201..6225c03 100644 --- a/historico.php +++ b/historico.php @@ -1,156 +1,156 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - - // Se pediu para limpar pendentes - if (isset($_POST['clear_pending'])) { - $pdo->exec("DELETE FROM comandos WHERE status = 'pending'"); - $_SESSION['flash'] = "🧹 Todos os comandos pendentes foram removidos!"; - header("Location: historico.php"); - exit; - } - - // Filtros - $where = []; - $params = []; - - if (!empty($_GET['uid'])) { - $where[] = "c.uid = ?"; - $params[] = $_GET['uid']; - } - if (!empty($_GET['status'])) { - $where[] = "c.status = ?"; - $params[] = $_GET['status']; - } - - $sql = "SELECT c.id, c.uid, c.cmd, c.status, c.queued_at, c.sent_at, c.ack_ts, c.error, - r.payload AS resposta, r.ts AS resposta_at - FROM comandos c - LEFT JOIN respostas r ON r.uid = c.uid - " . ($where ? "WHERE " . implode(" AND ", $where) : "") . " - ORDER BY c.queued_at DESC - LIMIT 50"; - - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - $comandos = $stmt->fetchAll(); - - $devices = $pdo->query("SELECT DISTINCT uid FROM devices ORDER BY uid ASC")->fetchAll(); - -} catch (Exception $e) { - die("Erro: " . $e->getMessage()); -} -?> - - - - - Histórico de Comandos - - - - - -
-

📜 Histórico de Comandos

- - -
- - - -
- -
- - -
-
- -
-
- -
-
- -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDDispositivoComandoStatusEnfileiradoEnviadoACKErroRespostaResp. At
-
- -
-
- - + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + + // Se pediu para limpar pendentes + if (isset($_POST['clear_pending'])) { + $pdo->exec("DELETE FROM comandos WHERE status = 'pending'"); + $_SESSION['flash'] = "🧹 Todos os comandos pendentes foram removidos!"; + header("Location: historico.php"); + exit; + } + + // Filtros + $where = []; + $params = []; + + if (!empty($_GET['uid'])) { + $where[] = "c.uid = ?"; + $params[] = $_GET['uid']; + } + if (!empty($_GET['status'])) { + $where[] = "c.status = ?"; + $params[] = $_GET['status']; + } + + $sql = "SELECT c.id, c.uid, c.cmd, c.status, c.queued_at, c.sent_at, c.ack_ts, c.error, + r.payload AS resposta, r.ts AS resposta_at + FROM comandos c + LEFT JOIN respostas r ON r.uid = c.uid + " . ($where ? "WHERE " . implode(" AND ", $where) : "") . " + ORDER BY c.queued_at DESC + LIMIT 50"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $comandos = $stmt->fetchAll(); + + $devices = $pdo->query("SELECT DISTINCT uid FROM devices ORDER BY uid ASC")->fetchAll(); + +} catch (Exception $e) { + die("Erro: " . $e->getMessage()); +} +?> + + + + + Histórico de Comandos + + + + + +
+

📜 Histórico de Comandos

+ + +
+ + + +
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDDispositivoComandoStatusEnfileiradoEnviadoACKErroRespostaResp. At
+
+ + +
+ + diff --git a/homeserver.yaml b/homeserver.yaml index 51f2e12..6599315 100644 --- a/homeserver.yaml +++ b/homeserver.yaml @@ -1,51 +1,51 @@ -server_name: "matrix.xupas.mywire.org" -pid_file: /data/homeserver.pid -listeners: - - port: 8008 - tls: false - type: http - x_forwarded: true - resources: - - names: [client, federation] - compress: false - -database: - name: psycopg2 - args: - user: synapse - password: synapse - database: synapse - host: matrix-postgres-1 - cp_min: 5 - cp_max: 10 - allow_unsafe_locale: true - -experimental_features: - msc3202_enabled: true - msc3077_enabled: true - msc3401_enabled: true - msc3391_enabled: true - msc3890_enabled: true - msc4153_enabled: true - -turn_uris: - - "turn:192.168.10.253:5349?transport=udp" - - "turn:192.168.10.253:5349?transport=tcp" -turn_shared_secret: "c246729fea23718edc2bf08726f6822f6732b1bde3c2e062194fc5e6758b0cf7" -turn_user_lifetime: 86400000 -turn_allow_guests: true - -log_config: "/data/matrix.xupas.mywire.org.log.config" -media_store_path: /data/media_store -registration_shared_secret: "c#PmLj2F,GAR,xFWL.uw-Gb0TF47oKD^Nb@a~h~D@WRVBHdo*y" -report_stats: true -macaroon_secret_key: "5XLRSbZP&ij+Dh=SC4^hUL2SjleMkIS69=uPsS&n6D8aLqJGqA" -form_secret: "ht7o4U6Ue@S7HBDVVJczKH*4h_gBkADg@ursU0Ye*RBIn7R8w8" -signing_key_path: "/data/matrix.xupas.mywire.org.signing.key" -trusted_key_servers: - - server_name: "matrix.org" - -suppress_key_server_warning: true -enable_registration: false -enable_registration_without_verification: false - +server_name: "matrix.xupas.mywire.org" +pid_file: /data/homeserver.pid +listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + resources: + - names: [client, federation] + compress: false + +database: + name: psycopg2 + args: + user: synapse + password: synapse + database: synapse + host: matrix-postgres-1 + cp_min: 5 + cp_max: 10 + allow_unsafe_locale: true + +experimental_features: + msc3202_enabled: true + msc3077_enabled: true + msc3401_enabled: true + msc3391_enabled: true + msc3890_enabled: true + msc4153_enabled: true + +turn_uris: + - "turn:192.168.10.253:5349?transport=udp" + - "turn:192.168.10.253:5349?transport=tcp" +turn_shared_secret: "c246729fea23718edc2bf08726f6822f6732b1bde3c2e062194fc5e6758b0cf7" +turn_user_lifetime: 86400000 +turn_allow_guests: true + +log_config: "/data/matrix.xupas.mywire.org.log.config" +media_store_path: /data/media_store +registration_shared_secret: "c#PmLj2F,GAR,xFWL.uw-Gb0TF47oKD^Nb@a~h~D@WRVBHdo*y" +report_stats: true +macaroon_secret_key: "5XLRSbZP&ij+Dh=SC4^hUL2SjleMkIS69=uPsS&n6D8aLqJGqA" +form_secret: "ht7o4U6Ue@S7HBDVVJczKH*4h_gBkADg@ursU0Ye*RBIn7R8w8" +signing_key_path: "/data/matrix.xupas.mywire.org.signing.key" +trusted_key_servers: + - server_name: "matrix.org" + +suppress_key_server_warning: true +enable_registration: false +enable_registration_without_verification: false + diff --git a/matrix_admin.php b/matrix_admin.php index 0b2a681..be6b273 100644 --- a/matrix_admin.php +++ b/matrix_admin.php @@ -1,279 +1,279 @@ - true, - CURLOPT_CUSTOMREQUEST => $method, - CURLOPT_HTTPHEADER => $headers, - CURLOPT_TIMEOUT => 60, - ]); - if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); - $resp = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - dbg("API RESP: HTTP=$code BODY=$resp"); - curl_close($ch); - return [$code, $resp]; -} - -// ============================== -// 🧼 USERNAME CLEANER FIX -// ============================== -function full_user(string $name, string $server): string { - dbg("RAW POST USER=[".$name."] HEX=[".bin2hex($name)."]"); - $clean = trim($name); - $clean = preg_replace('/[\x{00A0}\x{200B}\x{200C}\x{200D}\x{FEFF}]/u', '', $clean); - dbg("CLEAN USER=[".$clean."] HEX=[".bin2hex($clean)."]"); - if (preg_match('/^@[a-z0-9=_\-\.\/\+]+:[a-z0-9\.\-]+$/i', $clean)) return $clean; - if ($clean === '') return ''; - if ($clean[0] !== '@') $clean = '@' . $clean; - if (!str_contains($clean, ':')) $clean .= ':' . $server; - dbg("FINAL USER=[".$clean."] HEX=[".bin2hex($clean)."]"); - return $clean; -} - -// ============================== -// 🚦 PROCESSAMENTO DE AÇÕES -// ============================== -$action = $_POST['action'] ?? null; -$msg = ''; - -if ($action && $ADMIN_TOKEN) { - - dbg("=== ACTION: $action ==="); - dbg("POST DATA: " . json_encode($_POST)); - - // criar utilizador - if ($action === 'add_user') { - $user = full_user($_POST['user'], $SERVER_NAME); - $pass = trim($_POST['pass']); - [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ - "password" => $pass, - "deactivated" => false, - "admin" => false - ], $ADMIN_TOKEN); - $msg = ($code == 200 || $code == 201) ? "✅ Criado: $user" : "❌ Erro [$code]: $resp"; - } - - // alterar password - if ($action === 'change_pass') { - $user = full_user($_POST['user'], $SERVER_NAME); - $pass = trim($_POST['pass']); - [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/reset_password/" . urlencode($user), [ - "new_password" => $pass - ], $ADMIN_TOKEN); - $msg = ($code == 200) ? "🔑 Senha alterada para $user" : "❌ Erro [$code]: $resp"; - } - - // admin / unadmin - if ($action === 'make_admin' || $action === 'revoke_admin') { - $user = full_user($_POST['user'], $SERVER_NAME); - $is_admin = $action === 'make_admin'; - [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ - "admin" => $is_admin - ], $ADMIN_TOKEN); - $msg = ($code == 200) - ? ($is_admin ? "⬆️ $user agora é admin" : "⬇️ $user deixou de ser admin") - : "❌ Erro [$code]: $resp"; - } - - // ativar / desativar - if ($action === 'deactivate_user' || $action === 'reactivate_user') { - $user = full_user($_POST['user'], $SERVER_NAME); - $active = $action === 'reactivate_user'; - [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ - "deactivated" => !$active - ], $ADMIN_TOKEN); - $msg = ($code == 200) - ? ($active ? "♻️ $user reativado" : "🚫 $user desativado") - : "❌ Erro [$code]: $resp"; - } - - // apagar user - if ($action === 'delete_user') { - $user = full_user($_POST['user'], $SERVER_NAME); - [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/deactivate/" . urlencode($user), [ - "erase" => true - ], $ADMIN_TOKEN); - $msg = ($code == 200 || $code == 204) - ? "🗑️ $user removido permanentemente" - : "❌ Erro [$code]: $resp"; - } - - // ✅ NOVA: apagar mensagens do user (mantendo o user) - if ($action === 'erase_messages') { - $user = full_user($_POST['user'], $SERVER_NAME); - [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/user/$user/rooms", null, $ADMIN_TOKEN); - $rooms = json_decode($resp, true)['rooms'] ?? []; - $errors = []; - foreach ($rooms as $room) { - [$c, $r] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/purge_history/" . urlencode($room['room_id']), [ - "delete_local_events" => true, - "reason" => "Apagadas via painel" - ], $ADMIN_TOKEN); - if ($c != 200) $errors[] = "$room[room_id]: $r"; - } - $msg = empty($errors) ? "🧹 Mensagens de $user apagadas" : "❌ Erros: " . implode("; ", $errors); - } - - // ✅ NOVA: limpar metadados gerais - if ($action === 'clear_metadata') { - @unlink($DEBUG_LOG); - // aqui podes adicionar mais caminhos de logs/metadados - $msg = "⚡ Metadados limpos!"; - } -} - -// ============================== -// 📋 LISTAGEM USERS -// ============================== -$users = []; -if ($ADMIN_TOKEN) { - $next = null; - do { - $url = "$MATRIX_BASE/_synapse/admin/v2/users?limit=100"; - if ($next) $url .= "&from=" . urlencode($next); - [$code, $resp] = call_api("GET", $url, null, $ADMIN_TOKEN); - if ($code != 200) { dbg("ERRO AO LER USERS: HTTP=$code BODY=$resp"); break; } - $data = json_decode($resp, true); - $chunk = $data['users'] ?? $data['results'] ?? []; - $users = array_merge($users, $chunk); - $next = $data['next_token'] ?? null; - } while ($next); -} -?> - - - - -Matrix Admin - Xupas - - - -

💻 Matrix Admin - Xupas

- - -
- - -
-

➕ Criar Utilizador

-
- - - - -
-
- -
-

👥 Utilizadores

- - - - - - - - - - - - - - - - - -
UserAdminAtivoÚltimo LoginAções
- -
- - - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
-
-
- - -
-

⚡ Limpar metadados gerais

-
- - -
-
- - - + true, + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 60, + ]); + if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + dbg("API RESP: HTTP=$code BODY=$resp"); + curl_close($ch); + return [$code, $resp]; +} + +// ============================== +// 🧼 USERNAME CLEANER FIX +// ============================== +function full_user(string $name, string $server): string { + dbg("RAW POST USER=[".$name."] HEX=[".bin2hex($name)."]"); + $clean = trim($name); + $clean = preg_replace('/[\x{00A0}\x{200B}\x{200C}\x{200D}\x{FEFF}]/u', '', $clean); + dbg("CLEAN USER=[".$clean."] HEX=[".bin2hex($clean)."]"); + if (preg_match('/^@[a-z0-9=_\-\.\/\+]+:[a-z0-9\.\-]+$/i', $clean)) return $clean; + if ($clean === '') return ''; + if ($clean[0] !== '@') $clean = '@' . $clean; + if (!str_contains($clean, ':')) $clean .= ':' . $server; + dbg("FINAL USER=[".$clean."] HEX=[".bin2hex($clean)."]"); + return $clean; +} + +// ============================== +// 🚦 PROCESSAMENTO DE AÇÕES +// ============================== +$action = $_POST['action'] ?? null; +$msg = ''; + +if ($action && $ADMIN_TOKEN) { + + dbg("=== ACTION: $action ==="); + dbg("POST DATA: " . json_encode($_POST)); + + // criar utilizador + if ($action === 'add_user') { + $user = full_user($_POST['user'], $SERVER_NAME); + $pass = trim($_POST['pass']); + [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ + "password" => $pass, + "deactivated" => false, + "admin" => false + ], $ADMIN_TOKEN); + $msg = ($code == 200 || $code == 201) ? "✅ Criado: $user" : "❌ Erro [$code]: $resp"; + } + + // alterar password + if ($action === 'change_pass') { + $user = full_user($_POST['user'], $SERVER_NAME); + $pass = trim($_POST['pass']); + [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/reset_password/" . urlencode($user), [ + "new_password" => $pass + ], $ADMIN_TOKEN); + $msg = ($code == 200) ? "🔑 Senha alterada para $user" : "❌ Erro [$code]: $resp"; + } + + // admin / unadmin + if ($action === 'make_admin' || $action === 'revoke_admin') { + $user = full_user($_POST['user'], $SERVER_NAME); + $is_admin = $action === 'make_admin'; + [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ + "admin" => $is_admin + ], $ADMIN_TOKEN); + $msg = ($code == 200) + ? ($is_admin ? "⬆️ $user agora é admin" : "⬇️ $user deixou de ser admin") + : "❌ Erro [$code]: $resp"; + } + + // ativar / desativar + if ($action === 'deactivate_user' || $action === 'reactivate_user') { + $user = full_user($_POST['user'], $SERVER_NAME); + $active = $action === 'reactivate_user'; + [$code, $resp] = call_api("PUT", "$MATRIX_BASE/_synapse/admin/v2/users/" . urlencode($user), [ + "deactivated" => !$active + ], $ADMIN_TOKEN); + $msg = ($code == 200) + ? ($active ? "♻️ $user reativado" : "🚫 $user desativado") + : "❌ Erro [$code]: $resp"; + } + + // apagar user + if ($action === 'delete_user') { + $user = full_user($_POST['user'], $SERVER_NAME); + [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/deactivate/" . urlencode($user), [ + "erase" => true + ], $ADMIN_TOKEN); + $msg = ($code == 200 || $code == 204) + ? "🗑️ $user removido permanentemente" + : "❌ Erro [$code]: $resp"; + } + + // ✅ NOVA: apagar mensagens do user (mantendo o user) + if ($action === 'erase_messages') { + $user = full_user($_POST['user'], $SERVER_NAME); + [$code, $resp] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/user/$user/rooms", null, $ADMIN_TOKEN); + $rooms = json_decode($resp, true)['rooms'] ?? []; + $errors = []; + foreach ($rooms as $room) { + [$c, $r] = call_api("POST", "$MATRIX_BASE/_synapse/admin/v1/purge_history/" . urlencode($room['room_id']), [ + "delete_local_events" => true, + "reason" => "Apagadas via painel" + ], $ADMIN_TOKEN); + if ($c != 200) $errors[] = "$room[room_id]: $r"; + } + $msg = empty($errors) ? "🧹 Mensagens de $user apagadas" : "❌ Erros: " . implode("; ", $errors); + } + + // ✅ NOVA: limpar metadados gerais + if ($action === 'clear_metadata') { + @unlink($DEBUG_LOG); + // aqui podes adicionar mais caminhos de logs/metadados + $msg = "⚡ Metadados limpos!"; + } +} + +// ============================== +// 📋 LISTAGEM USERS +// ============================== +$users = []; +if ($ADMIN_TOKEN) { + $next = null; + do { + $url = "$MATRIX_BASE/_synapse/admin/v2/users?limit=100"; + if ($next) $url .= "&from=" . urlencode($next); + [$code, $resp] = call_api("GET", $url, null, $ADMIN_TOKEN); + if ($code != 200) { dbg("ERRO AO LER USERS: HTTP=$code BODY=$resp"); break; } + $data = json_decode($resp, true); + $chunk = $data['users'] ?? $data['results'] ?? []; + $users = array_merge($users, $chunk); + $next = $data['next_token'] ?? null; + } while ($next); +} +?> + + + + +Matrix Admin - Xupas + + + +

💻 Matrix Admin - Xupas

+ + +
+ + +
+

➕ Criar Utilizador

+
+ + + + +
+
+ +
+

👥 Utilizadores

+ + + + + + + + + + + + + + + + + +
UserAdminAtivoÚltimo LoginAções
+ +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

⚡ Limpar metadados gerais

+
+ + +
+
+ + + diff --git a/proxmox.php b/proxmox.php new file mode 100644 index 0000000..e2083b9 --- /dev/null +++ b/proxmox.php @@ -0,0 +1,211 @@ + true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query(['username' => PMX_USER, 'password' => PMX_PASS]), + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_TIMEOUT => 6, + ]); + $r = json_decode(curl_exec($ch), true); + curl_close($ch); + return $r['data'] ?? null; +} + +function pmx_get(string $path, array $auth): ?array { + $ch = curl_init(PMX_HOST.'/api2/json'.$path); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_TIMEOUT => 6, + CURLOPT_HTTPHEADER => ['Cookie: PVEAuthCookie='.$auth['ticket']], + ]); + $r = json_decode(curl_exec($ch), true); + curl_close($ch); + return $r['data'] ?? null; +} + +function fmt_bytes(int $b): string { + if ($b >= 1073741824) return round($b/1073741824, 1).' GB'; + if ($b >= 1048576) return round($b/1048576, 1).' MB'; + return round($b/1024, 1).' KB'; +} +function fmt_uptime(int $s): string { + $d = intdiv($s, 86400); $h = intdiv($s % 86400, 3600); $m = intdiv($s % 3600, 60); + return $d ? "{$d}d {$h}h" : ($h ? "{$h}h {$m}m" : "{$m}m"); +} +function pct_color(float $p): string { + if ($p >= 85) return '#f85149'; + if ($p >= 60) return '#d29922'; + return '#3fb950'; +} + +$auth = pmx_auth(); +$error = null; +$node = $containers = $vms = null; + +if (!$auth) { + $error = 'Não foi possível autenticar na API do Proxmox.'; +} else { + $node = pmx_get('/nodes/'.PMX_NODE.'/status', $auth); + $storage = pmx_get('/nodes/'.PMX_NODE.'/storage', $auth) ?? []; + $containers = pmx_get('/nodes/'.PMX_NODE.'/lxc', $auth) ?? []; + $vms = pmx_get('/nodes/'.PMX_NODE.'/qemu', $auth) ?? []; + usort($containers, fn($a,$b) => $a['vmid'] <=> $b['vmid']); + usort($vms, fn($a,$b) => $a['vmid'] <=> $b['vmid']); + $storage = array_filter($storage, fn($s) => ($s['active'] ?? 0) && ($s['total'] ?? 0) > 0); + usort($storage, fn($a,$b) => strcmp($a['storage'], $b['storage'])); +} + +$title = 'Proxmox'; +ob_start(); +?> + + + + +
+ + + +
+ + +
+
+
+
+ = 0): ?> +
+
+
+ +
+
+ + + +
+
+
+
+
de
+
+
+
+
+
+ + +
+ + + + +
+
+

Containers LXC ()

+
+
+ + + + + + + + + + + + + + +
IDNomeEstadoCPURAMUptime
running + + % + + + + + / + +
+
+
+ + + + +
+
+

VMs ()

+
+
+ + + + + + + + + + + + + + +
IDNomeEstadoCPURAMUptime
running + % + + + + + / + +
+
+
+ + + + +setUsername($config['username']) - ->setPassword($config['password']) - ->setKeepAliveInterval($config['keepalive']) - ->setConnectTimeout($config['timeout']) - ->setUseTls(false); // muda para true quando fores usar 8883/TLS - -$client = new MqttClient($config['host'], $config['port'], $config['clientId']); -$client->connect($settings, $config['clean']); -$client->publish($topic, $msg, $qos, $retain); -$client->disconnect(); - -header('Content-Type: application/json'); -echo json_encode(['ok'=>true,'topic'=>$topic,'qos'=>$qos,'retain'=>$retain]); +setUsername($config['username']) + ->setPassword($config['password']) + ->setKeepAliveInterval($config['keepalive']) + ->setConnectTimeout($config['timeout']) + ->setUseTls(false); // muda para true quando fores usar 8883/TLS + +$client = new MqttClient($config['host'], $config['port'], $config['clientId']); +$client->connect($settings, $config['clean']); +$client->publish($topic, $msg, $qos, $retain); +$client->disconnect(); + +header('Content-Type: application/json'); +echo json_encode(['ok'=>true,'topic'=>$topic,'qos'=>$qos,'retain'=>$retain]); diff --git a/save_note.php b/save_note.php index 32babb2..2a20da1 100644 --- a/save_note.php +++ b/save_note.php @@ -1,16 +1,16 @@ -prepare(" - UPDATE contadores - SET note = ? - WHERE uid = ? -"); - -$stmt->execute([$note, $uid]); - +prepare(" + UPDATE contadores + SET note = ? + WHERE uid = ? +"); + +$stmt->execute([$note, $uid]); + header("Location: console.php"); \ No newline at end of file diff --git a/send_ping.php b/send_ping.php index 7dd83d5..51c9c5b 100644 --- a/send_ping.php +++ b/send_ping.php @@ -1,25 +1,25 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -} catch (PDOException $e) { - die("Erro DB: " . $e->getMessage()); -} - -// ---------------- INPUT ---------------- -$uid = $_GET['uid'] ?? ''; - -if ($uid) { - $stmt = $db->prepare("INSERT INTO comandos (uid, cmd, params, status) - VALUES (:uid, 'PING', '{}', 'pending')"); - $stmt->execute([':uid' => $uid]); - // echo "PING enviado para {$uid}"; -} else { - echo "Falta o UID."; -} +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die("Erro DB: " . $e->getMessage()); +} + +// ---------------- INPUT ---------------- +$uid = $_GET['uid'] ?? ''; + +if ($uid) { + $stmt = $db->prepare("INSERT INTO comandos (uid, cmd, params, status) + VALUES (:uid, 'PING', '{}', 'pending')"); + $stmt->execute([':uid' => $uid]); + // echo "PING enviado para {$uid}"; +} else { + echo "Falta o UID."; +} diff --git a/vendor/php-mqtt/client/.github/release.yml b/vendor/php-mqtt/client/.github/release.yml index 374affd..2cd9ffd 100644 --- a/vendor/php-mqtt/client/.github/release.yml +++ b/vendor/php-mqtt/client/.github/release.yml @@ -1,25 +1,25 @@ -changelog: - exclude: - labels: - - ignore-for-release - authors: - - octocat - categories: - - title: Added - labels: - - enhancement - - title: Deprecated - labels: - - deprecated - - title: Removed - labels: - - removed - - title: Fixed - labels: - - bug - - title: Security - labels: - - security - - title: Changed - labels: - - "*" +changelog: + exclude: + labels: + - ignore-for-release + authors: + - octocat + categories: + - title: Added + labels: + - enhancement + - title: Deprecated + labels: + - deprecated + - title: Removed + labels: + - removed + - title: Fixed + labels: + - bug + - title: Security + labels: + - security + - title: Changed + labels: + - "*" diff --git a/vpn.php b/vpn.php new file mode 100644 index 0000000..006eb83 --- /dev/null +++ b/vpn.php @@ -0,0 +1,228 @@ + '192.168.10.100', 'user' => 'root', 'key' => $SSH_KEY]; + +$NORDVPNS = [ + 'lxc104' => ['label' => 'YouTube', 'icon' => '📺', 'host' => '192.168.10.104', 'desc' => 'WireGuard'], + 'lxc110' => ['label' => 'Torrents', 'icon' => '🧲', 'host' => '192.168.10.110', 'desc' => 'Transmission'], +]; + +$COUNTRIES = [ + 'Albania' => '🇦🇱', 'Australia' => '🇦🇺', 'Austria' => '🇦🇹', + 'Belgium' => '🇧🇪', 'Brazil' => '🇧🇷', 'Bulgaria' => '🇧🇬', + 'Canada' => '🇨🇦', 'Chile' => '🇨🇱', 'Colombia' => '🇨🇴', + 'Croatia' => '🇭🇷', 'Czech Republic' => '🇨🇿', 'Denmark' => '🇩🇰', + 'Estonia' => '🇪🇪', 'Finland' => '🇫🇮', 'France' => '🇫🇷', + 'Germany' => '🇩🇪', 'Greece' => '🇬🇷', 'Hong Kong' => '🇭🇰', + 'Hungary' => '🇭🇺', 'Iceland' => '🇮🇸', 'India' => '🇮🇳', + 'Ireland' => '🇮🇪', 'Israel' => '🇮🇱', 'Italy' => '🇮🇹', + 'Japan' => '🇯🇵', 'Latvia' => '🇱🇻', 'Lithuania' => '🇱🇹', + 'Luxembourg' => '🇱🇺', 'Malaysia' => '🇲🇾', 'Mexico' => '🇲🇽', + 'Moldova' => '🇲🇩', 'Netherlands' => '🇳🇱', 'New Zealand' => '🇳🇿', + 'Nigeria' => '🇳🇬', 'Norway' => '🇳🇴', 'Philippines' => '🇵🇭', + 'Poland' => '🇵🇱', 'Portugal' => '🇵🇹', 'Romania' => '🇷🇴', + 'Serbia' => '🇷🇸', 'Singapore' => '🇸🇬', 'Slovakia' => '🇸🇰', + 'Slovenia' => '🇸🇮', 'South Africa' => '🇿🇦', 'South Korea' => '🇰🇷', + 'Spain' => '🇪🇸', 'Sweden' => '🇸🇪', 'Switzerland' => '🇨🇭', + 'Taiwan' => '🇹🇼', 'Thailand' => '🇹🇭', 'Turkey' => '🇹🇷', + 'Ukraine' => '🇺🇦', 'United Kingdom' => '🇬🇧', 'United States' => '🇺🇸', +]; + +function do_ssh(string $host, string $user, string $key, string $cmd): string { + $opts = "-o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8"; + $ssh = "/usr/bin/ssh -i ".escapeshellarg($key)." $opts " + .escapeshellarg("$user@$host")." ".escapeshellarg($cmd)." 2>&1"; + return trim((string)shell_exec($ssh)); +} + +function parse_nordvpn_status(string $raw): array { + $connected = stripos($raw, 'Connected') !== false; + $country = '—'; $ip = '—'; + if (preg_match('/Country:\s*(.+)/i', $raw, $m)) $country = trim($m[1]); + if (preg_match('/IP:\s*(\S+)/i', $raw, $m)) $ip = trim($m[1]); + return compact('connected', 'country', 'ip'); +} + +function parse_wg_status(string $raw): array { + $parts = preg_split('/=== (wg\d+) ===/i', $raw, -1, PREG_SPLIT_DELIM_CAPTURE); + $ifaces = []; + for ($i = 1; $i < count($parts) - 1; $i += 2) { + $name = $parts[$i]; + $block = $parts[$i + 1]; + $peers = substr_count($block, 'peer:'); + $active = preg_match_all('/latest handshake: (\d+) (second|minute|hour)/i', $block); + $ip = '—'; + $ext = '—'; + if (preg_match('/addr:\s*(\S+)/i', $block, $m)) $ip = $m[1]; + if (preg_match('/ext:\s*(\S+)/i', $block, $m)) $ext = $m[1]; + $ifaces[$name] = ['peers' => $peers, 'active' => $active, 'ip' => $ip, 'ext' => $ext]; + } + return $ifaces; +} + +// AJAX +if (isset($_GET['ajax'])) { + header('Content-Type: application/json'); + $id = $_GET['ajax']; + if ($id === 'wg') { + $raw = do_ssh($WIREGUARD['host'], $WIREGUARD['user'], $WIREGUARD['key'], 'status'); + echo json_encode(['ifaces' => parse_wg_status($raw)]); + } elseif (isset($NORDVPNS[$id])) { + $raw = do_ssh($NORDVPNS[$id]['host'], 'root', $SSH_KEY, 'status'); + echo json_encode(parse_nordvpn_status($raw)); + } + exit; +} + +$result = null; +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $section = $_POST['section'] ?? ''; + $action = $_POST['action'] ?? ''; + if (isset($NORDVPNS[$section])) { + $vpn = $NORDVPNS[$section]; + if ($action === 'connect') { + $country = $_POST['country'] ?? ''; + if (isset($COUNTRIES[$country])) { + $raw = do_ssh($vpn['host'], 'root', $SSH_KEY, "connect $country"); + $result = ['id' => $section, 'ok' => stripos($raw, 'connected') !== false, 'msg' => $raw]; + } + } elseif ($action === 'disconnect') { + $raw = do_ssh($vpn['host'], 'root', $SSH_KEY, 'disconnect'); + $result = ['id' => $section, 'ok' => true, 'msg' => $raw]; + } + } +} + +$title = 'VPN'; +ob_start(); +?> + + + + +
+ +
+ + +
+ + + $vpn): ?> +
+
+

+ +
+
+ + +
+
+
+
A carregar…
+
+
+
+
+ + +
+ + + + +
+ +
+ + + +
+ +
+
+ + + +
+
+

🔒 WireGuard

+ LXC 100 +
+
+ 'Direto', 'wg1' => 'NordVPN'] as $iface => $desc): ?> +
+
+
+
+
+
A carregar…
+
+
+
+ +
+
+ +
+ + + +